
/*

  SmartClient Ajax RIA system
  Version v9.0p_2021-05-01/LGPL Deployment (2021-05-01)

  Copyright 2000 and beyond Isomorphic Software, Inc. All rights reserved.
  "SmartClient" is a trademark of Isomorphic Software, Inc.

  LICENSE NOTICE
     INSTALLATION OR USE OF THIS SOFTWARE INDICATES YOUR ACCEPTANCE OF
     ISOMORPHIC SOFTWARE LICENSE TERMS. If you have received this file
     without an accompanying Isomorphic Software license file, please
     contact licensing@isomorphic.com for details. Unauthorized copying and
     use of this software is a violation of international copyright law.

  DEVELOPMENT ONLY - DO NOT DEPLOY
     This software is provided for evaluation, training, and development
     purposes only. It may include supplementary components that are not
     licensed for deployment. The separate DEPLOY package for this release
     contains SmartClient components that are licensed for deployment.

  PROPRIETARY & PROTECTED MATERIAL
     This software contains proprietary materials that are protected by
     contract and intellectual property law. You are expressly prohibited
     from attempting to reverse engineer this software or modify this
     software for human readability.

  CONTACT ISOMORPHIC
     For more information regarding license rights and restrictions, or to
     report possible license violations, please contact Isomorphic Software
     by email (licensing@isomorphic.com) or web (www.isomorphic.com).

*/
//>	@class	Canvas
//
//		Canvas is the base abstraction for cross-browser DHTML drawing.
//		All DHTML widgets inherit from the Canvas class.
//
//  @treeLocation Client Reference/Foundation
//  @visibility external
//<

// declare the class itself
isc.ClassFactory.defineClass("Canvas");

// for speed, override the default isA function provided by ClassFactory.  The marker property
// "_isA_Canvas" is added below as both a class and instance property.
// Note that this approach could be extended to all classes via generating unique marker
// properties (so that iterating up the inheritance chain would not be required) but that would
// slow down init time, and isA.Canvas is 99% of the critical path usage anyway
isc.isA.Canvas = function (object) { return (object != null && object._isA_Canvas); };

// define groups for documentation purposes

	//>	@groupDef positioning
	//	Horizontal and vertical location on the page
	//<
	//>	@groupDef visibility
	//	Whether an object can be seen
	//<
	//>	@groupDef sizing
	// Width and height of an object
	//<
	//>	@groupDef appearance
	// Properties defining an object's appearance
	//<
	//>	@groupDef drawing
	// Rendering an object on the page.
    // <var class="smartgwt">Include the Drawing module your SmartGWT application
    // by including <code>&lt;inherits name="com.smartgwt.Drawing"/&gt;</code>
    // in your GWT module XML.</var>
	//<
	//>	@groupDef zIndex
	// Object's "stacking order" above or below other objects
	//<
	//>	@groupDef scrolling
	// Scrolling and clipping of objects when part of the object is obscured
	//<
	//>	@groupDef events
	// Handling mouse and keyboard events generated by the user
	//<
	//>	@groupDef containment
	// Canvases containing other objects as children or peers
	//<
	//>	@groupDef cues
	// Visual hints for the user that something can be done to this object
	//<
	//>	@groupDef dragdrop
	// Dragging objects and dropping them onto other objects
    // @title Drag and Drop
	//<
	//>	@groupDef image
	// Utilities to render images
	//<
	//>	@groupDef images
	// Referring to and loading images.
    // <P>
	// The two main URL settings relevant to loading images are:<br>
	// * imgDir (where application-specific images live)<br>
	// * skinImgDir (where system supplied images live)<br>
	//<
    //> @groupDef skins
    // Properties used to manage to the the overall appearance of the application.
    // <P>
    // A "skin" consists of
    // <ul>
    // <li><code>skin_styles.css</code>: a .css file defining the set of
    //     classes to apply to SmartClient components' visual elements</li>
    // <li><code>images/</code>: a directory of image files used as part of visual
    //     components</li>
    // <li><code>load_skin.js</code>: a .js file containing overrides for various
    //     SmartClient component properties that affect the appearance of those components.</li>
    // </ul>
    // Skins are loaded via the <code>skin</code> attribute of the +link{group:loadISCTag} or
    // by including the appropriate <code>load_skin.js</code> source file with a standard script
    // include tag.
    // <P>
    // To create a custom skin, we suggest making a complete copy of an existing skin, then
    // modifying the media, css class definitions and component property overrides you wish to
    // change.
    // <P>
    // Note that the <code>load_skin.js</code> file contains a +link{Page.setSkinDir()}
    // directive to set up the skin dir (used to ensure media is retrieved from the appropriate
    // directory), and a +link{Page.loadStyleSheet()} directive to load the .css file.
    // <P>
    // See the +link{group:skinning,Skinning Overview} for more information.
    //
    // @see group:appearance
    // @see group:images
    // @see group:files
    //<
	//>	@groupDef files
	// Referring to and loading other files.
    // <P>
	// The two main URL settings relevant to file loading are:<br>
	// * appDir  (where application-specific files live)<br>
	// * isomorphicDir (where system supplied files live)<br>
	//<

	//>	@groupDef utils
	// Misc utilities
    // @visibility internal
	//<
	//>	@groupDef form
	// Utilities to deal with forms and form elements.<P>
    //
    // Internal because DynamicForm exposes the functionality we support for forms - dealing with
    // forms directly is a minefield.
    // @visibility internal
	//<
	//>	@groupDef handles
	// Pointers to the DOM structures of objects that have been drawn
    // @visibility internal
	//<


//	Add class-level properties
//		You can access these properties on the static class object.
//		e.g.,	Canvas.myStaticProperty

isc.Canvas.addClassProperties({
    

    _isA_Canvas : true,

	//	Class constants
	AUTO:"auto",				//=	@const	isc.Canvas.AUTO			The "use default" setting.
	ANYTHING:"**anything**",	//=	@const	isc.Canvas.ANYTHING		Generally means "any value is acceptable".


	//>	@type	Positioning
	//			@visibility external
	//			@group	positioning
	ABSOLUTE:"absolute",		//	@value	isc.Canvas.ABSOLUTE		The canvas is absolutely positioned with respect to its parentElement.
	RELATIVE:"relative",		//	@value	isc.Canvas.RELATIVE		The canvas is relatively positioned according to the document flow.
	//<

	//>	@type	Visibility
	//			@visibility external
	//			@group	visibility
	INHERIT:"inherit",			//	@value	isc.Canvas.INHERIT		The widget visibility will match that of its parent (usually visible).
	VISIBLE:"visible",			//	@value	isc.Canvas.VISIBLE		The widget will always be visible whether its parent is or not.
	HIDDEN:"hidden",			//	@value	isc.Canvas.HIDDEN		The widget will always be hidden even when its parent is visible.
	//<

	//>	@type	DrawnState
	//			@group	drawing
	COMPLETE:"complete",
    //	@value	isc.Canvas.COMPLETE     the canvas is completely drawn, including children and peers, set up events, etc.
	DRAWN:"complete",
    //	@value	isc.Canvas.DRAWN        the canvas is completely drawn (synonym for isc.Canvas.COMPLETE)
	DRAWING_HANDLE:"drawingHandle",
    //	@value	isc.Canvas.DRAWING_HANDLE     the canvas is in the process of writing it's handle to the page / DOM
	HANDLE_DRAWN:"handleDrawn",
    //	@value	isc.Canvas.HANDLE_DRAWN     the canvas has completely written its handle to DOM
	UNDRAWN:"undrawn",
    //	@value	isc.Canvas.UNDRAWN     the canvas has not been drawn
    //<

	//>	@type Overflow
	// @visibility external
	//			@group	sizing
	//	@value	Canvas.VISIBLE		Content that extends beyond the widget's width or height is
    //                              displayed.
    //                              Note: To have the content be sized only by the drawn size of
    //                              the content set the overflow to be Canvas.VISIBLE and specify
    //                              a small size, allowing the size to expand to the size required
    //                              by the content.
    //                              Leaving the width / height for the widget undefined will use the
    //                              default value of 100, and setting the size to zero may cause the
    //                              widget not to draw.
	//	@value	Canvas.HIDDEN		Content that extends beyond the widget's width or height is
    //                              clipped (hidden).
	//	@value	Canvas.AUTO			Horizontal and/or vertical scrollbars are displayed only if
    //                              necessary. Content that extends beyond the remaining visible
    //                              area is clipped.
    //	@value	Canvas.SCROLL		Horizontal and vertical scrollbars are always drawn inside the
    //                              widget. Content that extends beyond the remaining visible area
    //                              is clipped, and can be accessed via scrolling.
	SCROLL:"scroll",
    //	@value	Canvas.CLIP_H		Clip horizontally but extend the canvas's clip region
    //                              vertically if necessary.
	CLIP_H:"clip-h",
    //	@value	Canvas.CLIP_V		Clip vertically but extend the canvas's clip region
    //                              horizontally if necessary.
	CLIP_V:"clip-v",
	//<

    

    //	@value	Canvas.IGNORE		Clipping is ignored by the ISC system. This setting may be used
    //                              for improved performance, with frequently-drawn widgets whose
    //                              dimensions always agree exactly with the size of their contents.
	IGNORE:"ignore",

	//>	@type	ScrollMechanism
	//			@group	scrolling
	NATIVE:"native",
    //	@value	isc.Canvas.NATIVE   Scroll by "native" mechanism - assigning directly to scrollLeft
    //                              and scrollTop
	CLIP:"clip",
    //	@value	isc.Canvas.CLIP     Scroll by repositioning / resizing handle and moving a clip
    //                              region as a viewport
	NESTED_DIV:"nestedDiv",
    //	@value	isc.Canvas.NESTED_DIV   Scroll by moving a handle around within an outer handle.
    //<

	//>	@type Alignment
	CENTER:"center",			//	@value	isc.Canvas.CENTER		Center within container.
	LEFT:"left",				//	@value	isc.Canvas.LEFT			Stick to left side of container.
	RIGHT:"right",				//	@value	isc.Canvas.RIGHT		Stick to right side of container.
	// @group appearance
	// @visibility external
	//<

	//>	@type VerticalAlignment
    //	@value	isc.Canvas.TOP			At the top of the container
	TOP:"top",
    //	@value	isc.Canvas.CENTER		Center within container.
    //	@value	isc.Canvas.BOTTOM       At the bottom of the container
	BOTTOM:"bottom",
	// @group appearance
	// @visibility external
	//<

    //> @type Side
    // Side of a component.
	//	@value	isc.Canvas.LEFT			Left side
	//	@value	isc.Canvas.RIGHT		Right side
    //	@value	isc.Canvas.TOP			Top side
    //	@value	isc.Canvas.BOTTOM       Bottom side
	// @visibility external
    //<


	//>	@type	Direction
	//			@visibility external
	//			@group	appearance
    //	@value	isc.Canvas.UP           above
	UP:"up",

    //	@value	isc.Canvas.DOWN         below
	DOWN:"down",
    //	@value	Canvas.LEFT			to the left of
    //	@value	Canvas.RIGHT		to the right of
	//<

    // other generic constants
    BOTH:"both",
    NONE:"none",
    VERTICAL:"vertical",
    HORIZONTAL:"horizontal",
    // layoutResizeBarPolicy constants
    MARKED:"marked",
    MIDDLE:"middle",
    ALL:"all",

	//>	@type	Cursor
    //
    // You can use whatever cursors are valid for your deployment platforms, but keep in mind that
    // visual representation may vary by browser and OS.
    //
    //	@value	Canvas.DEFAULT		Use the default arrow cursor for this browser/OS.
    //  @value  Canvas.AUTO         Use the default cursor for this element type in this browser/OS
    //	@value	Canvas.WAIT         Use the wait cursor.
    //	@value	Canvas.HAND			Use the hand cursor.
    //	@value	Canvas.MOVE			Use the "move" (crosshairs) cursor.
    //	@value	Canvas.HELP			Use the 'help' cursor.
    //	@value	Canvas.TEXT			Use the 'text' (i-beam) cursor.
    //  @value  POINTER             Use the normal hand pointer that appears when you hover over a link
    //  @value  "arrow"
    //  @value  "all-scroll"
    //  @value  "crosshair"         Use the 'crosshair' ( + ) cursor.
    //	@value	"col-resize" 	    Use the column resize cursor (horizontal double-tipped arrow)
    //	@value	"row-resize" 	    Use the row resize cursor (vertical double-tipped arrow)
    //	@value	"e-resize" 	        Use the "east resize" cursor.
    //	@value	"w-resize" 	        Use the "west resize" cursor.
    //	@value	"n-resize" 	        Use the "north resize" cursor.
    //	@value	"s-resize" 	        Use the "south resize" cursor.
    //	@value	"se-resize" 	    Use the "south-east resize" cursor.
    //	@value	"ne-resize" 	    Use the "north-east resize" cursor.
    //	@value	"nw-resize" 	    Use the "north-west resize" cursor.
    //	@value	"sw-resize" 	    Use the "south-west resize" cursor.
    //  @value  "not-allowed"       Use the "not-allowed" cursor.
    //
	//  @group	cues
	//	@see	attr:Canvas.cursor
    //
	//  @visibility external
    //  @example cursors
    //<
    //	@value	Canvas.ARROW		Synonym for "default"
    // NOTE: there is a difference between Canvas.DEFAULT and Canvas.AUTO - auto is as if there
    // was no cursor specified - if the element has text in it, the I-Beam text selection cursor
    // will show up when the user rolls over it. If the cursor is set to 'default' however the
    // cursor will show the standard default cursor (arrow) over the entire element.
	DEFAULT:"default",
	ARROW:"default",

	WAIT:"wait",
	// in Moz and in Safari/Chrome strict mode we have to use "pointer" rather than "hand"
	HAND:(isc.Browser.isMoz || (isc.Browser.isSafari && isc.Browser.isStrict)
	        || (isc.Browser.isIE && isc.Browser.version >= 9 && isc.Browser.isStrict)
	         ? "pointer" : "hand"),
	MOVE:"move",
	HELP:"help",
	TEXT:"text",
	CROSSHAIR:"crosshair",
    // Used for no-drop indication - not supported on Safari
    
    NOT_ALLOWED:"not-allowed",

    // NOTE: e-resize means "east resize".  On Windows, there's no distinction between east and west
    // resize (it's just horizontal resize), but on some OS' it may look directional, so we may need
    // to add a conditional to fall back to the "move" cursor on non-Windows platforms.
	COL_RESIZE:(isc.Browser.isIE && isc.Browser.version >= 6 ? "col-resize" : "e-resize"),

	ROW_RESIZE:(isc.Browser.isIE && isc.Browser.version >= 6 ? "row-resize" : "n-resize"),

	//>	@type	ImageStyle
	//			@visibility external
	//			@group	appearance
    //	@value	isc.Canvas.CENTER	Center (and don't stretch at all) the image if smaller than its enclosing frame.
	//CENTER:"center",
	//	@value	isc.Canvas.TILE		Tile (repeat) the image if smaller than its enclosing frame.
	TILE:"tile",
	//	@value	isc.Canvas.STRETCH	Stretch the image to the size of its enclosing frame.
	STRETCH:"stretch",
	//	@value	isc.Canvas.NORMAL   Allow the image to have natural size
	NORMAL:"normal",
	//<

	//>	@type BackgroundRepeat
    // Possible values for +link{canvas.backgroundRepeat}.
	REPEAT:"repeat", 			//	@value	isc.Canvas.REPEAT		Tile the background image horizontally and vertically.
	NO_REPEAT:"no-repeat",		//	@value	isc.Canvas.NO_REPEAT	Don't tile the background image at all.
	REPEAT_X:"repeat-x", 		//	@value	isc.Canvas.REPEAT_X		Repeat the background image horizontally but not vertically.
	REPEAT_Y:"repeat-y",		//	@value	isc.Canvas.REPEAT_Y		Repeat the background image vertically but not horizontally.
	// @group appearance
    // @visibility external
	//<

	//>	@type	Canvas.TextDirection
	//		Specifies RTL or LTR direction for text -- IE 5+ only
	//		Specify this to have your text show up "right to left" (rtl), eg: in Arabic or Hebrew
	//		Note: more efficient to leave blank for default of "left to right" (ltr)
	//	@group	appearance
	LTR:"ltr",		 		//	@value	isc.Canvas.LTR		Show text left-to-right (eg: English)
	RTL:"rtl",				//	@value	isc.Canvas.RTL		Show text right-to-left (eg: Arabic)
	//<

	//>	@type	Canvas.SnapDirection
	//		Specifies which direction to snap to, when snap-to-grid is enabled
	//	@group	dragdrop
	BEFORE:"before",    //  @value  isc.Canvas.BEFORE   Always snap up or left
	AFTER:"after",      //  @value  isc.Canvas.AFTER    Always snap down or right
	NEAREST:"nearest",  //  @value  isc.Canvas.NEAREST  Snap to the nearest grid point
	//<

	//>	@type	Canvas.SnapAxis
	//		Specifies which axis or axes we consider when snap-to-grid is enabled
	//	@group	dragdrop
	                    //  @value  isc.Canvas.HORIZONTAL   Snap on the horizontal axis
	                    //  @value  isc.Canvas.VERTICAL     Snap on the horizontal axis
	                    //  @value  isc.Canvas.BOTH         Snap on both axes
	//<



    

	// default zIndex for the next item to be drawn
	_nextZIndex:200000,

	// zIndex of the next item to be sent to the back
	_SMALL_Z_INDEX:199950,

	// zIndex of the next item to be brought to the front
	_BIG_Z_INDEX:800000,


	//>	@classAttr isc.Canvas.TAB_INDEX_GAP (integer : 80 : R)
	//		Specifies the gap to leave between automatically assigned tab indices for focusable
    //      canvii
    //<
    TAB_INDEX_GAP:50,

	//>	@classAttr isc.Canvas.TAB_INDEX_FLOOR (integer : 1000 : R)
	//		Specifies the lower limit for automatically assigned tab indices for focusable canvii.
    // @group focus
    // @visibility external
    //<
    TAB_INDEX_FLOOR:1000,

    //> @classAttr isc.Canvas.TAB_INDEX_CEILING (integer : 32766 : RA)
    // This is the native browser upper limit for tabIndices
    // @visibility internal
    //<
    
    TAB_INDEX_CEILING:32766,

    //>	@classAttr	isc.Canvas._imageCache		(array : [] : IRWA)
	//			cache to hold images, so we avoid loading them over and over unnecessarily
	//		@group	images
	//<
	_imageCache:[],

    // List of CSS attributes that apply to text only:
    textStyleAttributes : [ "fontFamily", "fontSize", "color", "backgroundColor",
                            "fontWeight", "fontStyle", "textDecoration", "textAlign"
                            // Optionally also include: fontSizeAdjust, fontVariant, whiteSpace
                          ],

    // IE Filter settings
    // ---------------------------------------------------------------------------------------


    // Preventing style doubling
    // In various places we render widgets as a <table> inside a <div> but want to
    // allow a single CSS style for the combined DOM structure.
    // In this case we have to re-apply the css class applied to the widget to the TD as otherwise
    // text based styling options will not be applied because it doesn't cascade through table
    // elements.
    // However we DON'T want every CSS setting to apply to both the outer <div> and inner <td>
    // or we end up with (for example) doubled borders.
    // We usually handle this by writing out explicit "null" styling options on the TD to
    // override the properties we don't want doubled from the css class. These options then take
    // presidence over the attributes specified in the CSS class.
    // Use this central string to clear out
    // - margin, border, padding, bg color, filter (eg CSS gradient), background-image

    // Note: called once on framework init. May be called after framework init due to
    // changing the value of neverUseFilters / allowExternalFilters etc.
    _doublingStringObservers:[],
    _setDoublingStrings : function () {
        this._$noStyleDoublingCSS = isc.Browser.isIE && (!this.neverUseFilters || this.allowExternalFilters)
                ? "margin:0px;border:0px;padding:0px;background-image:none;background-color:transparent;-webkit-box-shadow:none;box-shadow:none;filter:none;"
                : "margin:0px;border:0px;padding:0px;background-image:none;background-color:transparent;-webkit-box-shadow:none;box-shadow:none;";
        // NOTE: actually a constant used in button rendering
        isc.Canvas.addProperties({
            _$tableNoStyleDoubling : "' style='" + isc.Canvas._$noStyleDoublingCSS
        });
        // Since this string is dynamic (see setNeverUseFilters()), but may be cached
        // in various places, those places will need to be notified when it changes
        // We're using standard 'target' / 'methodName' terminology here and passing in the
        // new string.
        for (var i = 0; i < this._doublingStringObservers.length; i++) {
            var callback = this._doublingStringObservers[i];
            if (callback.target == null || callback.target.destroyed) continue;
            callback.target[callback.methodName](this._$noStyleDoublingCSS);
        }
    },

    //> @groupDef IEFilters
    // In order to compensate for various bugs and missing features in Internet Explorer, it's
    // necessary to use Microsoft-proprietary "filter" settings, as follows:
    // <ul>
    // <li> IE6-8: Opacity filter required for opacity to work at all
    // <li> IE6: AlphaImageLoader filter required for PNG transparency to work at all
    // <li> IE7-8: AlphaImageLoader filter required for PNG transparency to work properly with
    //      opacity (eg, translucent rounded windows), otherwise, PNGs will turn entirely black
    //      or show other severe artifacts when opacity is applied
    // </ul>
    // Using these filters has a range of side-effects:
    // <ul>
    // <li> AlphaImageLoader will cause the UI to appear frozen until users have downloaded all
    //      PNG media shown on the page
    // <li> moderate to severe impact on rendering speed (20-60%)
    // <li> font smoothing is disabled
    // </ul>
    // <P>
    // For an application that is frequently used (where images will typically be cached) on
    // recent machines, and where font smoothing is not considered important, no special steps
    // need to be taken.
    // <P>
    // If any of the above side effects are important, our recommendations are:
    // <ul>
    // <li> minimize use of PNG media - use .gif instead
    // <li> for IE7-8, +link{canvas.neverUsePNGWorkaround,disable AlphaImageLoader} and
    //      +link{canvas.useOpacityFilter,disable Opacity} globally since these browsers
    //      can only render PNGs correctly in the absence of opacity settings.  Selectively
    //      enable opacity only in widgets that do not contain PNGs (eg the modalMask shown by
    //      a Window).  Avoid the use of opacity fades as a transition effect for IE unless you
    //      have eliminated all or almost all PNG media and the remaining artifacts are considered
    //      acceptable.  Also eliminate all use of filter effects in CSS, and
    //      +link{canvas.allowExternalFilters,disable the workaround} that makes this possible.
    // <li> if IE6 performance is critically important, eliminate all PNG media and all use of
    //      opacity and +link{canvas.neverUseFilters,disable all filters}.
    // </ul>
    // Note that the .gif format does not support partially transparent pixels, hence can't be
    // used for very high-quality antialiasing effects.  However, certain specific tools can
    // produce high-quality anti-aliased images in the less known PNG8 format, and this
    // particular format has the least artifacts in the above situations.  Details
    // +externalLink{http://blogs.sitepoint.com/2007/09/18/png8-the-clear-winner/, here}.
    //
    // @title Internet Explorer "filter" effects
    // @visibility external
    //<

    //> @classAttr Canvas.neverUsePNGWorkaround (boolean : null : IR)
    // If set, the AlphaImageLoader IE filter will never be used.   Does not remove
    // AlphaImageLoader usage in already-drawn components.
    // <P>
    // See +link{group:IEFilters} for background.
    //
    // @group IEFilters
    // @visibility external
    //<

    //> @classAttr Canvas.neverUseFilters (boolean : null : IR)
    // Disables automatic use of filters in IE by default.  Filters will only be used if
    // +link{canvas.useOpacityFilter} is explicitly set to true on a component.
    // <P>
    // Does not remove filters on already drawn components, or which are applied via CSS.
    // <P>
    // See +link{group:IEFilters} for background.
    //
    // @group IEFilters
    // @visibility external
    //<

    //> @classMethod Canvas.setNeverUseFilters()
    // Changes the system-wide +link{Canvas.neverUseFilters} setting.
    // @param neverUseFilters (boolean) new setting
    //
    // @group IEFilters
    // @visibility external
    //<
    setNeverUseFilters : function (neverUseFilters) {
        this.neverUseFilters = neverUseFilters;
        this._setDoublingStrings();
    },

    //> @classAttr Canvas.allowExternalFilters (boolean : true : IR)
    // If enabled, uses a moderately expensive workaround to allow the use of IE filters in CSS
    // to produce gradient effects for buttons, grid rows, and other elements, without the use
    // of image backgrounds.
    // <P>
    // See +link{group:IEFilters} for background.
    //
    // @group IEFilters
    // @visibility external
    //<
    allowExternalFilters:true,

    //> @classMethod Canvas.setAllowExternalFilters()
    // Changes the system-wide +link{Canvas.allowExternalFilters} setting.
    // @param allExternalFilters (boolean) new setting
    //
    // @group IEFilters
    // @visibility external
    //<
    setAllowExternalFilters : function (allowExternalFilters) {
        this.allowExternalFilters = allowExternalFilters;
        this._setDoublingStrings();
    },

    //> @attr canvas.useOpacityFilter (boolean : null : IR)
    // Configures where the Opacity filter is used for IE6-8.
    // <P>
    // With the default of null, opacity filters are used unless
    // +link{classAttr:Canvas.neverUseFilters} has been set.  When set explicitly to true,
    // opacity filters are used even if <code>neverUseFilters</code> is true.
    // <P>
    // See +link{group:IEFilters} for background.
    //
    // @group IEFilters
    // @visibility external
    //<


    // Delayed Redraw
	// -----------------------------------------------------------------------------------------
    //>	@classAttr	isc.Canvas._redrawQueue		(array of canvas objects : [] : IRWA)
	//			array to hold pointers to canvases that need to be redrawn
	//			these items will be redrawn automatically after "a little while"
	//		@group	handles
	//		@see	Canvas.clearRedrawQueue()
	//<
	_redrawQueue:[],

    //>	@classAttr	Canvas._redrawQueueDelay		(number : 0 : IRWA)
	//			(msec) delay after which canvases that need to be redrawn are actually redrawn
	//		@group	handles
	//		@see	Canvas.clearRedrawQueue()
	//<
    // NOTE: redraws are generally done on a timer because it batches many changes which require
    // redraws into a single redraw.  Redraws can be done immediately, in specific circumstances
    // like drag resizing, in order to provide more immediate response.
    _redrawQueueDelay:(0),
    _delayedAdjustOverflowQueueDelay:200,

    // Stats and global Canvas tracking
	// -----------------------------------------------------------------------------------------
    //>	@classAttr	isc.Canvas._canvasList		(array of canvas objects : [] : IRWA)
	//			array to hold pointers to all of the canvases that have been created
	//			so we can clear them out later
	//		@group	handles
	//		@platformNotes	Used in IE only to clear all handles when the page is unloaded.
	//		@see	Canvas._clearDOMHandles()
	//<
	_canvasList:[],

    // count of canvases which are flagged as _iscInternal used e.g. in the Developer Console to
    // report the end-user canvas count number
    _iscInternalCount: 0,

    // object where we record/update various statistics
    _stats : {
        redraws:0,
        clears:0,
        destroys:0,
        draws:0
        // NOTE: number allocated is just Canvas._canvasList.length
    },
    // object for tracking redraws by widget ID
    _redraws : {
    },

    // cache for partwise-event handler names
    _partHandlers : {},

    // Wrapping HTML in Canvii
	// -----------------------------------------------------------------------------------------
    _wrapperCanvasStack : [],

    
    useMozBackMasks : false,

    //> @classAttr canvas.useNativeWheelDelta (boolean : true: RWA)
    // If set, use the magnitude of the wheel delta as reported by the browser
    // to estimate the OS wheel sensitivity setting. Currently, this only applies
    // to Firefox, and controls whether or not SCROLL_PAGE_UP or SCROLL_PAGE_DOWN
    // events are parsed (a facility which is wholly undocumented). All other browsers
    // return wheelDeltas in multiples of 120, depending on how fast the user
    // scrolls the wheel, and regardless of OS sensitivity settings.
    // @group scrolling
    // @visibility internal
    //<
    useNativeWheelDelta: true,

    //> @classAttr canvas.scrollWheelDelta (number : 50 : RWA)
    // 
    // How far should this canvas be scrolled in response to mouse scroll wheel
    // events? This value governs how far the canvas will scroll, in pixels, when the
    // user moves the mouse wheel by the smallest possible increment.
    // <P>
    // For most browsers, this is the sole option controlling the sensitivity of the 
    // scroll wheel, though some systems allow the user to configure the sensitivity of
    // the mouse wheel.
    // <P>
    // For Firefox, this value represents the distance to scroll when the user 
    // moves the mousewheel by the smallest possible increment with a browser/OS
    // configured wheel sensitivity of 3 lines. If the sensitivity is increased or reduced, 
    // the scroll scroll distance will be equal to scrollWheelDelta * (lines/3).<br>
    // If the OS is set to page-at-a-time scrolling, for Firefox, the distance scrolled is the 
    // height of the window, rounded down to a multiple of scrollWheelDelta.
    // <P>
    // See also +link{EventHandler.getWheelDelta()}.
    // @group scrolling
    //<
    scrollWheelDelta: 50,

    //> @classAttr canvas.loadingImageSrc (SCImgURL : "[SKINIMG]loadingSmall.gif" : RWA)
    // Image URL to be displayed while data is being loaded (if enabled for the widget
    // waiting for data). Must be square; +link{loadingImageSize} specifies the width
    // and height.
    // @group animation
    // @see listGrid.loadingDataMessage
    // @see detailViewer.loadingMessage
    // @see HTMLFlow.loadingMessage
    // @see ViewLoader.loadingMessage
    // @visibility external
    //<
    loadingImageSrc: "[SKINIMG]loadingSmall.gif",

    //> @classAttr canvas.loadingImageSize (integer : 16 : RWA)
    // Specifies the width and height of +link{loadingImageSrc}.
    // @group animation
    // @visibility external
    //<
    loadingImageSize: 16
});
isc.Canvas._setDoublingStrings();

isc.Canvas.addProperties({

    _isA_Canvas : true,

	//> @attr	canvas.ID		(string : null : IR)
	// Global identifier for referring to a widget in JavaScript.  The ID property is optional if
    // you do not need to refer to the widget from JavaScript, or can refer to it indirectly
    // (for example, by storing the reference returned by +link{class.create,create()}).
    // <P>
    // An internal, unique ID will automatically be created upon instantiation for any canvas
    // where one is not provided.
    // <P>
    // The ID property should be unique in the global scope. If <code>window[<i>ID</i>]</code>
    // is already assigned to something else a warning will be logged using the developer console,
    // and the existing reference will be replaced, calling +link{Canvas.destroy(),destroy()} on the
    // previous object if it is a SmartClient Class instance.
    // <P>
    // Automatically generated IDs will be unique as long as the canvases they refer to remain
    // active - once a canvas with an automatically generated ID has been destroyed, its ID may be
    // reused for the next canvas created with no explicitly specified ID.
    //
    // @group basics
    // @visibility external
	//<

    //>	@attr	canvas.autoDraw		(Boolean : true : IR)
    // If true, this canvas will draw itself immediately after it is created.
    // <P>
    // <b>Note</b> that you should turn this OFF for any canvases that are provided as children
    // of other canvases, or they will draw initially, then be clear()ed and drawn again when
    // added as children, causing a large performance penalty.
    // <P>
    // For example, the following code is incorrect and will cause extra draw()s:
    // <P>
    // <pre>
    //     isc.Layout.create({
    //         members : [
    //             isc.ListGrid.create()
    //         ]
    //     });
    // </pre>
    // It should instead be:
    // <pre>
    //     isc.Layout.create({
    //         members : [
    //             isc.ListGrid.create(<b>{ autoDraw: false }</b>)
    //         ]
    //     });
    // </pre>
    // In order to avoid unwanted autoDrawing systematically, it is recommend that you call
    // +link{classMethod:isc.setAutoDraw(),isc.setAutoDraw(false)} immediately after SmartClient is loaded
    // and before any components are created, then set <code>autoDraw:true</code> or call
    // draw() explicitly to draw components.
    // <P>
    // Otherwise, if the global setting for autoDraw remains <code>true</code>, you must set
    // autoDraw:false, as shown above, on every component in your application that
    // should not immediately draw: all Canvas children, Layout members, Window items, Tab
    // panes, etc, however deeply nested.  Forgetting to set autoDraw:false will result in one
    // more clear()s - these are reported on the Results tab of the
    // +link{group:debugging,Developer Console}, and can be tracked to individual components by
    // using the "clears" log category in the Developer Console.
    //
    //  @example autodraw
    //  @visibility external
    //  @group	drawing
    //<
    autoDraw:true,

    // Children and Peers
	// --------------------------------------------------------------------------------------------

    //> @attr   canvas.parentElement    (Canvas : null : [IRA])
    // This Canvas's immediate parent, if any.
    // <BR>
    // Can be initialized, but any subsequent manipulation should be via
    // +link{canvas.addChild(),addChild()} and +link{canvas.removeChild(),removeChild()} calls
    // on the parent.
    //
    //  @visibility external
    //  @group  containment
    //<

    //> @attr   canvas.topElement    (Canvas : null : [RA])
    // The top-most Canvas (i.e., not a child of any other Canvas), if any, in this widget's
    // containment hierarchy.
    //  @visibility external
    //  @group  containment
    //<

    //> @attr   canvas.masterElement    (Canvas : null : [RA])
    // This Canvas's "master" (the Canvas to which it was added as a peer), if any.
    //  @visibility external
    //  @group  containment
    //<

    //> @attr canvas.children (Array of Canvas : null : IR)
    // Array of all Canvii that are immediate children of this Canvas.
    // <P>
    // Use +link{addChild()} and +link{removeChild()} to add and remove children after a Canvas
    // has been created/drawn.
    //
    // @visibility external
    // @group containment
    //<

    //> @attr canvas.peers (Array of Canvas : null : IRA)
    // Array of all Canvii that are peers of this Canvas.
    // <P>
    // Use +link{addPeer()} and +link{removePeer()} to add and remove peers after a Canvas
    // has been created/drawn.
    //
    // @visibility external
    // @group  containment
    //<

    //> @attr canvas.allowContentAndChildren (boolean : true : [IA])
    // If true this widget supports having content specified via the content property and
    // children specified in the normal way.  Normally, if children are present, content
    // returned from getInnerHTML() is shown but is not refreshed by redraw().
    // <P>
    // Enabling entails a small performance reduction on redraw()s.
    // @visibility internal
    //<
    
    allowContentAndChildren:true,

    //> @attr canvas.drawChildrenThenContent (boolean : false : [IA])
    // If true, and this widget supports having content and children, when this widget is
    // drawn, the children will be written into the handle, then the content will be created
    // and inserted before the first child in the DOM.
    // <P>
    // This is used by widgets who need to create content based on the rendered size of their
    // children.
    // @visibility internal
    //<

    // --------------------------------------------------------------------------------------------

    //> @pseudoClass DrawContext
    // Object that expresses the position in the DOM where a Canvas should draw itself, used
    // for insertion into an existing DOM structure.
    // @treeLocation Client Reference/Foundation/Canvas
    // @group drawContext
    // @visibility drawContext
    //<

    //> @attr drawContext.element (DOMElement : null : [IRA])
    // Element in the DOM
    // @group drawContext
    // @visibility drawContext
    //<

    //> @attr drawContext.position (DrawPosition : "beforeBegin" : [IRA])
    // Position where Canvas should be inserted relative to <code>drawContext.element</code>.
    // @group drawContext
    // @visibility drawContext
    //<

    //> @type DrawPosition
    // @value "beforeBegin" insert before the target element
    // @value "afterBegin"  insert as the target element's first child
    // @value "beforeEnd"   insert as the target element's last child
    // @value "afterEnd"    insert after the target element
    // @value "replace"     replace the target element
    // @group drawContext
    // @visibility external
    //<

    //> @attr  canvas.drawContext (DrawContext : null : [IRWA])
    // Location in the DOM where this Canvas should draw itself, specified as an existing DOM
    // element and a position relative to that element.
    // <P>
    // This feature is intended for temporary integration with legacy page architectures only;
    // the native browser's reaction to DOM insertion is unspecified and unsupported.  For
    // consistent cross-browser layout and positioning semantics, use Canvas parents
    // (especially Layouts) and use absolute positioning at top level.
    // <P>
    // NOTE: persistence of drawContext: if a Canvas is clear()d and then draw()n again, it will
    // keep the same drawContext unless the <code>drawContext.position</code> was "replace".<P>
    // If a Canvas is added as a child to Canvas parent, its drawContext will be dropped.
    // @group drawContext
    // @visibility drawContext
    //<

    

    // HTMLElement
    //> @attr canvas.htmlElement (HTML Element : null : IRWA)
    // If specified as a pointer to an HTML element present in the DOM, this canvas will be
    // rendered inside that element on +link{Canvas.draw(), draw()}.
    // <P>
    // <i>NOTES:</i><br>
    // This feature is intended for temporary integration with legacy page architectures only;
    // the native browser's reaction to DOM insertion is unspecified and unsupported. For
    // consistent cross-browser layout and positioning semantics, use Canvas parents
    // (especially Layouts) and use absolute positioning at top level.
    // <P>
    // Persistence of htmlElement: If +link{canvas.htmlPosition} is set to <code>"replace"</code>
    // the htmlElement will be removed from the DOM when the canvas is drawn - therefore the
    // htmlElement attribute will be cleared at this time.
    // Otherwise if a Canvas is clear()d and then draw()n again it will
    // be rendered inside the same htmlElement.<br>
    // If a Canvas is added as a child to Canvas parent, its htmlElement will be dropped.
    // <P>
    // +link{canvas.position} should typically be set to <code>"relative"</code> if the widget
    // is to be rendered inline within a standard page.
    // @group htmlElement, positioning
    // @visibility external
    //<


    //> @attr canvas.htmlPosition (DrawPosition : "afterBegin" : [IRWA])
    // If +link{canvas.htmlElement} is specified, this attribute specifies the position where
    // the canvas should be inserted relative to the <code>htmlElement</code> in the DOM.
    // @group htmlElement, positioning
    // @visibility external
    //<
    htmlPosition:"afterBegin",

    //> @attr canvas.matchElement (boolean : null : [IRWA])
    // If +link{canvas.htmlElement} is specified, should this canvas initially be drawn
    // at the same dimensions as the htmlElement?<br>
    // Note: setting this property will not force the canvas to resize if the element
    // subsequently resizes (for example due to page reflow).
    // @visibility external
    //<

    // Positioning
	// --------------------------------------------------------------------------------------------

    //>	@attr	canvas.position		(Positioning : null : IRWA)
    // Absolute or relative, corresponding to the "absolute" (with respect to parent) or
    // "relative" (with respect to document flow) values for the CSS position attribute.
    // <P>
    // Setting <code>position:"relative"</code> enables SmartClient components to be embedded
    // directly into the native HTML flow of a page, causing the component to be rendered
    // within an existing DOM structure.
    // This attribute should only be set to <code>"relative"</code> on a top level component
    // (a component with no +link{canvas.parentElement}).
    // <P>
    // There are 2 ways to embed relatively positioned canvases in the DOM - by default the
    // component will be written out inline when it gets +link{canvas.draw(),drawn()n}. For example
    // to embed a canvas in an HTML table you could use this code:
    // <pre>
    // &lt;table&gt;
    //   &lt;tr&gt;
    //     &lt;td&gt;
    //       &lt;script&gt;
    //         isc.Canvas.create({autoDraw:true, backgroundColor:"red", position:"relative"});
    //       &lt;/script&gt;
    //     &lt;td&gt;
    //   &lt;/tr&gt;
    // &lt;/table&gt;
    // </pre>
    // Alternatively you can make use of the +link{canvas.htmlElement} attribute.
    // <P>
    // Relative positioning is intended as a short-term integration scenario while incrementally
    // upgrading existing applications.
    // Note that relative positioning is not used to manage layout within SmartClient components -
    // instead the +link{class:Layout} class would typically be used.
    // For best consistency and flexibility across browsers, all SmartClient layout managers
    // use absolute positioning.
    // <P>
    // For canvases with a specified +link{canvas.htmlElement}, this attribute defaults to
    // <code>"relative"</code>. In all other cases the default value will be
    // <code>"absolute"</code>.
    //
    // @visibility external
    // @group positioning
    // @example inlineComponents
    //<
	position:null,

    //>	@attr canvas.left (Number or String : 0 : IRW)
    // Number of pixels the left side of the widget is offset to the right from its default
    // drawing context (either its parent's topleft corner, or the document flow, depending on
    // the value of the +link{position} property).
    // <P>
    // Can also be set as a percentage, specified as a String ending in '%', eg, "50%".  In
    // this case the top coordinate is considered as a percentage of the specified width of
    // the +link{canvas.parentElement,parent}.
    //
    // @visibility external
    // @group  positioning
    //<
	left:0,

    //>	@attr canvas.top (Number or String : 0 : IRW)
    // Number of pixels the top of the widget is offset down from its default drawing context
    // (either its parent's top-left corner, or the document flow, depending on the value of
    // the +link{position} property).
    // <P>
    // Can also be set as a percentage, specified as a String ending in '%', eg, "50%".  In
    // this case the top coordinate is considered as a percentage of the specified height of
    // the +link{canvas.parentElement,parent}.
    //
    // @visibility external
    // @group  positioning
    //<
	top:0,

    // Sizing
	// --------------------------------------------------------------------------------------------

    // Notes on width/height vs defaultWidth/defaultHeight:
    //
    // Layouts will resize widgets that don't have their height/width explicitly set.
    // Important to use defaultHeight/defaultWidth to set defaults for a widget, otherwise the
    // defaults will be taken to be the "fixed" size of the widget, and its size will not be
    // managed by layouts
    // In some cases, we WANT certain dimensions to be regarded as fixed by a Layout (they may
    // still be overridden by the user) so we set the height/width properties

    //>	@attr	canvas.width		(Number or String : null : [IRW])
    // Size for this component's horizontal dimension.
    // <P>
    // Can be a number of pixels, or a percentage like "50%". Percentage sizes are resolved to
    // pixel values as follows:
    // <UL>
    // <LI>If a canvas has a specified +link{canvas.percentSource,percentSource}, sizing will be
    //     a percentage of the size of that widget (see also +link{canvas.percentBox}).</LI>
    // <LI>Otherwise, if a canvas has a +link{canvas.masterElement,masterElement}, and
    //     +link{Canvas.snapTo,snapTo} is set for the widget, sizing will be a percentage of
    //     the size of that widget (see also +link{canvas.percentBox}).</LI>
    // <LI>Otherwise if this is a child of some other canvas, percentages will be based on the
    //     inner size of the +link{canvas.parentElement,parentElement}'s viewport.</LI>
    // <LI>Otherwise, for top level widgets, sizing is calculated as a percentage of page size.</LI>
    // </UL>
    // <P>
    // +link{Layout,Layouts} may specially interpret percentage sizes on their children,
    // and also allow "*" as a size.
    // <P>
    // Note that if +link{Canvas.overflow,overflow} is set to "visible", this size is a
    // minimum, and the component may overflow to show all content and/or children.
    // <P>
    // If trying to establish a default width for a custom component, set
    // +link{Canvas.defaultWidth,defaultWidth} instead.
    //
    //  @visibility external
    //  @group	sizing
    //  @setter setWidth
    //  @getter getWidth
    //<

    //>	@attr	canvas.height		(Number or String : null : [IRW])
    // Size for this component's vertical dimension.
    // <P>
    // Can be a number of pixels, or a percentage like "50%". See documentation for
    // +link{canvas.width} for details on how percentage values are resolved actual size.
    // <P>
    // Note that if +link{Canvas.overflow,overflow} is set to "visible", this size is a
    // minimum, and the component may overflow to show all content and/or children.
    // <P>
    // If trying to establish a default height for a custom component, set
    // +link{Canvas.defaultHeight,defaultHeight} instead.
    //
    //  @visibility external
    //  @group	sizing
    //  @setter setHeight
    //  @getter getHeight
    //<
    

    //>	@attr canvas.defaultWidth (int : 100 : IRWA)
    // For custom components, establishes a default width for the component.
    // <P>
    // For a component that should potentially be sized automatically by a Layout, set this
    // property rather than +link{width} directly, because Layouts regard a width setting as
    // an explicit size that shouldn't be changed.
    //
    //  @visibility external
	//  @group	sizing
	//<
    defaultWidth:100,

    //>	@attr canvas.defaultHeight (int : 100 : IRWA)
    // For custom components, establishes a default height for the component.
    // <P>
    // For a component that should potentially be sized automatically by a Layout, set this
    // property rather than +link{height} directly, because Layouts regard a height setting as
    // an explicit size that shouldn't be changed.
    //
	// @group sizing
    // @visibility external
	//<
    defaultHeight:100,

    //>	@attr canvas.minWidth (number : 10 : IRWA)
    // Minimum width that this Canvas can be resized to.
    // <P>
    // Note that a Canvas with overflow:"visible" has an implicit minimize size based on it's
    // contents.
    // <p>
    // Note that <code>minWidth</code> affects only user-initiated drag resizes.  To set the
    // minimum width of a Canvas embedded in a Layout, you can set +link{layout.minMemberSize}
    // to affect the minimum width of all members of the Layout.
    //
	// @group sizing
    // @visibility external
	//<
	minWidth:10,

    //>	@attr canvas.maxWidth (number : 10000 : IRWA)
    // Maximum width that this Canvas can be resized to.
    //
	// @group sizing
    // @visibility external
	//<
	maxWidth:10000,

    //>	@attr canvas.minHeight (number : 10 : IRWA)
    // Minimum height that this Canvas can be resized to by a user.
    // <p>
    // Note that a Canvas with overflow:"visible" has an implicit minimum size based on it's
    // contents.
    // <p>
    // Note that <code>minHeight</code> affects only user-initiated drag resizes.  To set the
    // minimum height of a Canvas embedded in a Layout, you can set +link{layout.minMemberSize}
    // to affect the minimum height of all members of the Layout.
    //
	// @group sizing
    // @visibility external
	//<
	minHeight:10,

    //>	@attr canvas.maxHeight (number : 10000 : IRWA)
    // Maximum height that this Canvas can be resized to.
    //
	// @group sizing
    // @visibility external
	//<
	maxHeight:10000,

	// --------------------------------------------------------------------------------------------

	//>	@attr	canvas.allowNativeContentPositioning    (boolean : false : IRW)
    //  Allow HTML content which includes native elements that use relative or absolute positioning.
    //<
    

	//>	@attr	canvas.zIndex		(number : Canvas.AUTO | Canvas.AUTO : IRWA)
    //
    // Stacking order of this Canvas with respect to other content and components on the page.
    //
    // The default zIndex of "auto" means that a zIndex will be decided at draw time,
    // so that if many Canvii are draw with zIndex "auto", the last Canvas drawn is on top.
    // <p>
    // If you want native HTML content to appear in front of this canvas, set zIndex to zero.
    //
	// @group	zIndex
	// @value	(number)
	// @value	Canvas.AUTO
	//<
	zIndex:isc.Canvas.AUTO,

    //>	@attr	canvas.autoShowParent		(Boolean : false : IRWA)
    //      If set to true, the widget's parent (if any) will automatically be shown whenever the
    //      widget is shown.
    //  @visibility external
    //  @group appearance
    //<
	autoShowParent:false,

    //>	@attr	canvas.visibility		(Visibility : isc.Canvas.INHERIT : IRW)
    //      Controls widget visibility when the widget is initialized. See Visibility type for
    //      details.
    //  @getter isVisible
    //  @setter show, hide
    //  @visibility external
    //  @group appearance
    //<
	visibility:isc.Canvas.INHERIT,

    //> @attr canvas.hideUsingDisplayNone (boolean : false : IRA)
    // When this widget is hidden (see +link{Canvas.visibility} and +link{Canvas.hide()}),
    // should display:none be applied to the +link{Canvas.getOuterElement(),outer element}?
    // <p>
    // This setting is not supported for general use, but in certain cases, it has been shown
    // that display:none is a work-around for browser bugs involving burn-through of iframes or
    // plugins, where the content of the iframe or plugin may still be visible despite the
    // containing widget being hidden.
    // @group appearance
    // @visibility external
    //<
    
    hideUsingDisplayNone: false,

    //>	@attr canvas.canSelectText		(Boolean : false : IRWA)
	// Whether native drag selection of contained text is allowed within this Canvas.
    // <P>
    // Note that setting this property to <code>false</code> will not avoid text selection
    // which is initiated outside this Canvas from continuing into this Canvas, even if text
    // selection began in another Canvas.
    //
	//		@group	events
    // @visibility external
	//<

	//>	@type CSSStyleName
    // CSS class name to apply to some HTML element on this page. This is a string that should
    // match the css class defined for the page in an external stylesheet or in inline
    // html &lt;STYLE&gt; tags.
    // <P>
    // As a general rule, wherever it is possible to provide a CSS styleName (such as
    // +link{Canvas.styleName} or +link{Button.baseStyle}, your CSS style can specify border,
    // margins, padding, and any CSS attributes controlling background or text styling.  You
    // should not specify any CSS properties related to positioning, clipping, sizing or
    // visibility (such as "overflow", "position", "display", "visibility" and "float") - use
    // SmartClient APIs for this kind of control.
    // <P>
    // Because text wrapping cannot be consistently controlled cross-browser from CSS alone,
    // you should use SmartClient properties such as +link{Button.wrap} instead of the
    // corresponding CSS properties, when provided.
    // <P>
    // Content contained within SmartClient components can use arbitrary CSS, with the
    // caveat that the content should be tested on all supported browsers, just as content
    // outside of SmartClient must be.
    // <P>
    // <b>Special note on CSS margins</b>: wherever possible, use CSS padding and border in
    // lieu of CSS margins, or non-CSS approaches such as +link{Layout.layoutMargin},
    // +link{Canvas.snapTo}, or absolute positioning (including specifying percentage left/top
    // coordinates).  We recommend this because CSS specifies a very complicated and widely
    // criticized "margin-collapse" behavior which surprising effects when margins exist on
    // both parents and children.  Compounding the problem, margins are implemented very
    // differently on different browsers, especially when it comes to HTML margins.
    //
	// @group appearance
	// @visibility external
    // @example consistentSizing
    //<

    //> @type CSSColor
    // CSS color specification applied to a specific HTML element on this page.
    // <P>
    // This is a string matching the syntax as specified in CSS1, and can be
    // formatted in one of the following ways:
    // <ul>
    // <li>A keyword color, &ldquo;white&rdquo;</li>
    // <li>Six-digit hex notation, &ldquo;#ffffff&rdquo;</li>
    // <li>Three-digit hex notation, &ldquo;#fff&rdquo;</li>
    // <li>8-bit decimal notation, &ldquo;rgb(255, 255, 255)&rdquo;</li>
    // <li>Percentage notation, &ldquo;rgb(100%, 100%, 100%)&rdquo;</li>
    // </ul>
    //
	// @group appearance
	// @visibility external
    //<

    //>	@attr canvas.className		(CSSStyleName : "normal" : [IRW])
    // The CSS class applied to this widget as a whole.
    // @group appearance
    // @visibility external
    // @deprecated In favor or +link{canvas.styleName} as of SmartClient release 5.5
    //<

    //> @attr canvas.styleName    (CSSStyleName : "normal" : [IRW])
    // The CSS class applied to this widget as a whole.
    // @group appearance
    // @setter canvas.setStyleName()
    // @visibility external
    // @example styles
    //<
	styleName:"normal",

	//>	@attr	canvas.textDirection	(TextDirection : null : IRW)
	//			Use this to specify a text direction for the canvas:
	//					Canvas.LTR (left to right, eg English)
	//					Canvas.RTL (right to left, eg Arabic)
	//			Leave as null to pick up the text direction automatically
	//			 from that set at the Page level, set to one of the above to override.
	//		@group	textDirection
	//		@platformNotes	IE only.
	//<

	//>	@attr canvas.eventProxy		(canvas object : null : IRWA)
	// Set to another canvas to have that process events for us.
	// Useful for event processing of peers (borders, decorators, etc.)
	// @group events
	//<

    //> @type HTMLString
    // A String of HTML, such as "<span class='somestyle'>text</span>".
    // <P>
    // In many contexts, such as +link{button.title} and +link{ListGrid.formatCellValue()}, an
    // HTML String can be specified, allowing you to use normal HTML tags and CSS to do
    // formatting or styling.
    // <P>
    // However, bear in mind that if you attempt any kind of layout or advanced styling in such
    // an HTML string, different browsers may render the HTML differently - use SmartClient
    // +link{Layout,layout} and +link{canvas.styleName,styling} features wherever possible to
    // avoid this.  See also +link{type:CSSStyleName}.
    //
    // @visibility external
    //<

    //>	@attr canvas.contents		(HTMLString : "&nbsp;" : IRWA)
    // The contents of a canvas or label widget. Any HTML string is acceptable.
    //
    // @see dynamicContents
    // @group contents
    // @visibility external
    //<
	contents:isc.nbsp,

    //> @attr canvas.dynamicContents (Boolean : false : IRWA)
    //
    // Dynamic contents allows the contents string to be treated as a simple, but powerful
    // template.  When this attribute is set to true, expressions of the form \${arbitrary JS
    // here} are replaced by the result of the evaluation of the JS code inside the curly
    // brackets.  This evaluation happens at draw time.  If you want to trigger a re-evaluation
    // of the expressions in the contents string you can call markForRedraw() on the canvas.
    // <p>
    // You can use this feature to build some simple custom components. For example, let's say
    // you want to show the value of a Slider in a Canvas somewhere on the screen.  You can do
    // this by observing the valueChanged() method on the slider and calling setContents() on
    // your canvas with the new string or you can set the contents of the canvas to something
    // like:
    // <p><code>
    // "The slider value is \${sliderInstance.getValue()}."
    // </code><p>
    // Next you set dynamicContents: true on the canvas, observe valueChanged() on the slider
    // and call canvas.markForRedraw() in that observation.  This approach is cleaner than
    // setContents() when the Canvas is aggregating several values or dynamic expressions.
    // Like so:
    // <p>
    // <pre>
    // Slider.create({
    //     ID: "mySlider"
    // });
    //
    // Canvas.create({
    //     ID: "myCanvas",
    //     dynamicContents: true,
    //     contents: "The slider value is \${mySlider.getValue()}."
    // });
    //
    // myCanvas.observe(mySlider, "valueChanged",
    //                  "observer.markForRedraw()");
    // </pre>
    // You can embed an arbitrary number of dynamic expressions in the contents string.  The
    // search and replace is optimized for speed.
    // <p>
    // If an error occurs during the evaluation of one of the expressions, a warning is logged
    // to the ISC Developer Console and the error string is embedded in place of the expected
    // value in the Canvas.
    // <p>
    // The value of a function is its return value.  The value of any variable is the same as
    // that returned by its toString() representation.
    // <p>
    // Inside the evaluation contentext, <code>this</code> points to the canvas instance that
    // has the dynamicContents string as its contents - in other words the canvas instance on
    // which the template is declared.
    //
    // @see contents
    // @see canvas.dynamicContentsVars
    // @example dynamicContents
    // @group contents
    // @visibility external
	//<

    //> @attr canvas.dynamicContentsVars (ValueMap : null : IRWA)
    //
    // An optional map of name:value parameters that will be available within the scope of the
    // dynamicContents evaluation.  For example - if you have e.g:
    // <pre>
    // Canvas.create({
    //   dynamicContents: true,
    //   dynamicContentsVars: {
    //       name: "Bob"
    //   },
    //   contents: "hello \${name}"
    // });
    // </pre>
    // The above will create a canvas with contents <code>hello Bob</code>.  You can add, remove, and
    // change values in the dynamicContentsVars object literal, just call
    // <code>markForRedraw()</code> on the canvas to have the dynamicContents template re-evaluated.
    // <p>
    // Note that <code>this</code> is always available inside a dynamic contents string and points to
    // the canvas instance containing the dynamic contents.
    // <p>
    // Used only if +link{attr:Canvas.dynamicContents} : true has been set.
    //
    // @see dynamicContents
    // @visibility external
    //<



    // Per-Canvas CSS overrides.
	// --------------------------------------------------------------------------------------------
    // Consider defining a style for the individual Canvas instead of using these overrides, since
    // this makes that Canvas skinnable from CSS.

	//>	@attr canvas.margin (number : null : IRW)
    // Set the CSS Margin, in pixels, for this component.  Margin provides blank space outside
    // of the border.
    // <P>
    // This property sets the same thickness of margin on every side.  Differing per-side
    // margins can be set in a CSS style and applied via +link{styleName}.
    // <P>
    // Note that the specified size of the widget will be the size <b>including</b> the margin
    // thickness on each side.
    //
    // @visibility external
	// @group appearance
	//<

	//>	@attr canvas.padding (number : null : IRW)
    // Set the CSS padding of this component, in pixels.  Padding provides space between the
    // border and the component's contents.
    // <P>
    // This property sets the same thickness of padding on every side.  Differing per-side
    // padding can be set in a CSS style and applied via +link{styleName}.
    // <P>
    // Note that CSS padding does not affect the placement of +link{canvas.children}.  To
    // provide a blank area around children, either use +link{canvas.margin,CSS margins} or use
    // a +link{Layout} as the parent instead, and use properties such as
    // +link{layout.layoutMargin} to create blank space.
    //
    // @visibility external
	// @group appearance
	//<

    //>	@attr canvas.border (string : null : IRW)
    // Set the CSS border of this component, as a CSS string including border-width,
    // border-style, and/or color (eg "2px solid blue").
    // <P>
    // This property applies the same border to all four sides of this component.  Different
    // per-side borders can be set in a CSS style and applied via +link{styleName}.
	// <p>
    // If +link{canvas.isGroup} is set to true then border is derived from the 
    // +link{canvas.groupBorderCSS} attribute, not from the explicit border property.
	//
    // @visibility external
	// @group appearance
	//<

    //>	@attr canvas.backgroundColor (string : null : IRW)
	// The background color for this widget. It corresponds to the CSS background-color
    // attribute. You can set this property to an RGB value (e.g. #22AAFF) or a named color
    // (e.g. red) from a list of browser supported color names.
    //
    // @visibility external
	// @group appearance
	//<

	//>	@attr canvas.backgroundImage (SCImgURL : null : IRW)
	// URL for a background image for this widget (corresponding to the CSS "background-image"
    // attribute).
    // @visibility external
	// @group appearance
	//<

    //>	@attr canvas.backgroundRepeat (BackgroundRepeat : null : IR)
    // Specifies how the background image should be tiled if this widget
    // is larger than the image. It corresponds to the CSS <code>background-repeat</code>
    // attribute.
    // <p>
    // The default of null means no <code>background-repeat</code> CSS will be
    // written out.  See +link{BackgroundRepeat} type for details on other settings.
    // <p>
    // NOTE: this setting directly sets the CSS property <code>background-repeat</code> but
    // does not attempt to work around various known bugs with this setting, or lack of support
    // in IE6.  If you need to apply CSS-based workarounds for browser limitations with
    // this setting, it's best to do so via setting +link{canvas.styleName}.
    //
    // @visibility external
	// @group appearance
    //<
	backgroundRepeat:isc.Canvas.REPEAT,

    //>	@attr	canvas.backgroundPosition		(string : null : IR)
    //      Specifies how the background image should be positioned on the widget.
    //      It corresponds to the CSS background-position attribute. If unset,
    //      no background-position attribute is specified if a background image is
    //      specified.
    // @visibility external
	// @group appearance
    //<

    //>	@attr	canvas.mozOutlineOffset (string : "-1px": [IRA])
    // Only applies to Moz Firefox 1.5 and above.
    // When this widget receives focus, how far should the dotted focus outline appear from
    // the edge of the canvas. A negative value will render the dotted outline inside the
    // canvas
    // @visibility internal
    //<
    mozOutlineOffset:"-1px",

    //>	@attr	canvas.mozOutlineColor (string : null : [IRA])
    // Only applies to Moz Firefox 1.5 and above.
    // When this widget receives focus, what color should the dotted focus outline appear.
    // Unspecified by default - gives us the native browser behavior.
    // @visibility internal
    //<
    //mozOutlineColor:null,

    // Skinning
	// --------------------------------------------------------------------------------------------

    //>	@attr	canvas.appImgDir		(URL : "" : IRWA)
	// Default directory for app-specific images, relative to the Page-wide
    // +link{Page.getAppImgDir(),appImgDir}.
    // @group images
    // @visibility external
	//<
	appImgDir:"",

    //>	@attr	canvas.skinImgDir		(URL : "images/" : IRWA)
	// Default directory for skin images (those defined by the class), relative to the
    // Page-wide +link{Page.getSkinDir(),skinDir}.
    // @group images
    // @visibility external
	//<
	skinImgDir:"images/",

	// --------------------------------------------------------------------------------------------

    //>	@attr	canvas.cursor		(Cursor : Canvas.DEFAULT : IRWA)
    //      Specifies the cursor image to display when the mouse pointer is
    //      over this widget. It corresponds to the CSS cursor attribute. See Cursor type for
    //      different cursors.
    //  @visibility external
    //  @group  cues
    //  @example dragCreate
    //  @example cursors
    //<
	cursor:isc.Canvas.DEFAULT,

    //>	@attr	canvas.disabledCursor       (Cursor : Canvas.DEFAULT : IRWA)
    //      Specifies the cursor image to display when the mouse pointer is
    //      over this widget if this widget is disabled. It corresponds to the CSS cursor
    //      attribute. See Cursor type for different cursors.
    //  @visibility external
    //  @group  cues
    //<
    disabledCursor:isc.Canvas.DEFAULT,

	//>	@attr	canvas.noDropCursor       (Cursor : Canvas.NOT_ALLOWED : IRWA)
    //      Specifies the cursor image to display when the user drags something over this widget
    //      after +link{this.setNoDropIndicator()} has been called.<br>
    //      Default cursor type <code>"not-allowed"</code> is not supported in Safari browsers.
    //      We therefore also provide the alternative +link{canvas.shouldSetNoDropTracker}
    //      no-drop indicator functionality.
    //  @group  cues
    //<
    noDropCursor:isc.Canvas.NOT_ALLOWED,

    //>	@attr	canvas.opacity		(number : null : IRWA)
    //      Renders the widget to be partly transparent. A widget's opacity property may
    //      be set to any number between 0 (transparent) to 100 (opaque).
	//		Null means don't specify opacity directly, 100 is fully opaque.
	//		Note that heavy use of opacity may have a performance impact on some older
	//      browsers.
	//      <P>
	//      In older versions of Internet Explorer (Pre IE9 / HTML5), opacity is achieved
	//      through proprietary filters. If
	//      +link{canvas.neverUseFilters,filters have been disabled} within this application
	//      developers must set +link{canvas.useOpacityFilter} to true for specific components
	//      on which opacity support is required.
	//      <P>
	//      Also note that opacity is incompatible
	//      with +link{canvas.useBackMask,backMasks}.
	//
    //  @visibility external
    //  @setter setOpacity()
	//  @group	cues
	//<
    

    //> @attr canvas.smoothFade (boolean : null : [IRWA])
    // Avoids a visible flash (native browser repaint) for canvases when setting opacity
    // to / from 100% in  Mozilla browsers.
    // @visibility internal
    //<


    
    //>Moz
    _useMozOpacity : (isc.Browser.isMoz && isc.Browser.geckoVersion < 20081201),
    //<Moz

    //>	@attr	canvas.overflow		(Overflow : Canvas.VISIBLE : [IRW])
    //			Controls what happens when the drawn size of the content of a Canvas is either
    //			greater or smaller than the specified size of the Canvas.  Similar to the CSS
    //			property overflow, but consistent across browsers.  See Overflow type for
    //			details.
    //  @visibility external
    //  @setter setOverflow()
	//  @group  sizing
	//<
	overflow:isc.Canvas.VISIBLE,

    //>	@attr canvas.alwaysShowVScrollbar (boolean : false : [IRWA])
    // If this canvas has <code>overflow</code> set to <code>"auto"</code>, and is showing
    // custom scrollbars, settting this property to true will ensure that a custom vertical
    // scrollbar is shown even if the scrollHeight of the widget is less than the specified
    // height
    //  @visibility internal
	//  @group  sizing
	//<
    
	alwaysShowVScrollbar:false,

    //>	@attr canvas.forceHandleOverflowHidden (boolean : ? : [IR])
    // Controls whether the handle's CSS overflow value is forced to "hidden" even
    // if the value of canvas.overflow would normally set it to a different value.
    // Only applies to IE Browsers; set automatically in certain certain situations.
    //  @visibility internal
	//  @group  sizing
	//<
    forceHandleOverflowHidden: false,

    // Scrolling
	// --------------------------------------------------------------------------------------------

    

	//>	@attr	canvas.showCustomScrollbars		(Boolean : true : IRWA)
    // Whether to use the browser's native scrollbars or SmartClient-based scrollbars.
    // <P>
    // SmartClient-based scrollbars are skinnable, giving you complete control over look and
    // feel.  SmartClient-based scrollbars also enable some interactions not possible with
    // native scrollbars, such as +link{ListGrid.fixedRecordHeights,variable height records}
    // in grids in combination with +link{listGrid.dataPageSize,data paging}.
    // <P>
    // Native browser scrollbars are slightly faster simply because there are less SmartClient
    // components that need to be created, drawn and updated.  Each visible SmartClient-based
    // scrollbar on the screen has roughly the impact of two StretchImgButtons.
    // <P>
    // SmartClient is always aware of the size of the scrollbar, regardless of whether native
    // or custom scrollbars are used, and regardless of what operating system and/or operating
    // system "theme" or "skin" is in use.  This means SmartClient will correctly report the
    // +link{canvas.getViewportHeight(),viewport size}, that is, the interior area of the
    // widget excluding space taken by scrollbars, which is key for exactly filling a component
    // with content without creating unnecessary scrolling.
    // <P>
    // The <code>showCustomScrollbars</code> setting is typically overridden in load_skin.js in
    // order to change the default for all SmartClient components at once.
    // This may be achieved via the static +link{Canvas.setShowCustomScrollbars()} method or
    // via a simple addProperties block, like so:
    // <pre>
    //     isc.Canvas.addProperties({ showCustomScrollbars:false });
    // </pre>
    //
	// @group scrolling
    // @visibility external
	//<
    showCustomScrollbars :
    // NOTE: leading logical NOT, so the rest of this conditional specifies the conditions in which
    // we use native scrollbars
    !(
        // use native scrollbars on IE5+
        (isc.Browser.isOpera || isc.Browser.isIE && isc.Browser.version > 4) ||
        
        (isc.Browser.isUnix && isc.Browser.isMoz && isc.Browser.geckoVersion >= 20020826
                                                 && isc.Browser.geckoVersion <= 20031007)

    ),

    //>	@attr	canvas.scrollbarSize		(number : 16 : IRWA)
	//			How thick should we make the scrollbars for this canvas.<br>
	//			NOTE: has no effect if showCustomScrollbars is false.
	//		@group	scrolling
    //      @visibility external
    //      @see getScrollbarSize()
	//<
	scrollbarSize:16,

	// NOTE: the following properties only apply when showCustomScrollbars is true

    //>	@attr canvas.scrollbarConstructor (String : "Scrollbar" : [IA])
	// The class that will be used to create custom scrollbars for this component. Set this
	// attribute to a Scrollbar subclass with e.g. a different skinImgDir, to customize scrollbar
	// appearance for this component only.
	// @group	scrolling
    // @visibility external
	//<
	scrollbarConstructor:"Scrollbar",

    //>	@attr	canvas.scrollLeft		(number : 0 : IRWA)
	//			number of pixels that this canvas is shifted leftwards due to scrolling.
	//		@group	scrolling
	//<
	scrollLeft:0,
    _scrollRight:0,

	//>	@attr	canvas.scrollTop		(number : 0 : IRWA)
	//			number of pixels that this canvas is shifted upwards due to scrolling.
	//		@group	scrolling
	//<
	scrollTop:0,

    //>     @attr   canvas.scrollDelta (number : 20 : RWA)
    // Amount to scroll when the scroll button is pressed
    //              @group  scrolling
    //<
    scrollDelta:20,

    // Disabling
	// --------------------------------------------------------------------------------------------

    //>	@attr canvas.disabled (boolean : false : IRW)
    // If set to true, the widget will be disabled. A widget is only considered enabled
    // if it is individually enabled and all parents above it in the containment hierarchy
    // are enabled. This allows you to enable or disable all components of a complex
    // nested widget by enabling or disabling the top-level parent only.
    //
    // @getter isDisabled
    // @setter enable, disable
    // @group enable
    // @visibility external
    //<
    //disabled:false,

    //> @attr   canvas.enabled  (boolean : "unset" : IRWA)
    // If set to true, this widget will be enabled, if set to false, or null, this
    // widget will be disabled.
    // @visibility external
    // @group enable
    // @deprecated As of SmartClient version 5.5 this property has been deprecated. The
    //   +link{canvas.disabled} property will be used to govern enabled/disabled state instead
    //  if <code>this.enabled</code> has not been changed from its default value.
    //<
    
    _$unset:"unset",
    enabled:"unset",

    //>	@attr	canvas.redrawOnDisable		(boolean : false : IRWA)
	//			do we redraw when the disabled state changes ?
	//		@group	drawing, enable
	//<
	redrawOnDisable:false,

    //> @attr  canvas.redrawOnEnable       (boolean : false : IRWA)
    // do we redraw when the enabled state changes ?
    // @group  drawing, enable
    // @deprecated As of SmartClient 5.5 use +link{canvas.redrawOnDisable} instead
    //<

    // Peers: for which actions should we mimic what the master does?
	// --------------------------------------------------------------------------------------------

    //>	@attr	canvas._redrawWithMaster		(boolean : true : IRWA)
	//		For a peer, should we redraw automatically when our masterElement is redrawn?
	//		@group	drawing, containment
	//<
	_redrawWithMaster:true,

	//>	@attr	canvas._resizeWithMaster		(boolean : true : IRWA)
	//		For a peer, should we resize automatically when our masterElement is resized?
	//		@group	drawing, containment
	//<
    _resizeWithMaster:true,

	//>	@attr	canvas._moveWithMaster		(boolean : true : IRWA)
	//		For a peer, should we move automatically when our masterElement moves?
	//		@group	drawing, containment
	//<
    _moveWithMaster:true,

    //> @attr   canvas._setOpacityWithMaster    (boolean : true : IRWA)
    // For a peer, should our opacity be automatically updated to match that of our
    // masterElement that changes?
    // @group drawing, containment
    //<
    _setOpacityWithMaster:true,


	//>	@attr	canvas.redrawOnResize		(Boolean : true : IRWA)
	// Should this element be redrawn in response to a resize?
    // <P>
    // Should be set to true for components whose +link{getInnerHTML,inner HTML} will not
    // automatically reflow to fit the component's new size.
    //
	// @group drawing
	// @visibility external
	//<


    //>	@attr	canvas._showWithMaster (boolean : true : IRWA)
	//		For a peer, should we be shown automatically when our master is shown?
	//		@group	drawing, containment
	//<
	_showWithMaster:true,


	// --------------------------------------------------------------------------------------------

    //>	@attr	canvas._redrawWithParent		(boolean : true : IRWA)
	//		Should we redraw automatically when our parentElement is redrawn?
	//		Turn this off ONLY if you're completely committed to redrawing an element
	//		 manually yourself.
	//		@group	drawing, containment
	//<
	_redrawWithParent:true,

    // Focus
	// --------------------------------------------------------------------------------------------

    //>	@attr	canvas.hasFocus		(boolean : false : IRWA)
	// Do we have the focus?
	//		@group	focus
	//<

    //>	@attr	canvas.canFocus		(boolean : null : IRWA)
    // Can this widget be allowed to become the target of keyboard events?
    // <P>
    // If canFocus is unset (the default), only scrollable widgets with visible scrollbars are
    // focusable, to allow for keyboard scrolling.
    // <P>
    // A widget normally receives focus by being clicked on or tabbed to.
    //
    //		@group	focus, events
    //      @setter setCanFocus()
    // @visibility external
    // @example focus
	//<

    //> @attr   canvas.showFocusOutline    (boolean : true : IRWA)
    // For focusable widgets, should the native dotted focus outline be shown, where supported?
    // @visibility internal
    //<
    showFocusOutline:true,

    //>	@attr	canvas.redrawOnFocus		(boolean : false : IRWA)
    //			should we redraw automatically when this object accepts the focus?
    //		@group	drawing, focus
    //<

    //> @attr   canvas.tabIndex (number : null : IRWA)
    // If specified this governs the tabIndex of the widget in the page's tab order.
    // Note that by default SmartClient auto-assigns tab-indices, ensuring focusable widgets
    // are reachable by tabbing in the order in which they are drawn on the page.
    // <code>canvas.tabIndex</code> cannot be set to greater than
    // +link{classAttr:Canvas.TAB_INDEX_FLOOR} - as we reserve the values above this range for
    // auto-assigned tab-indices.
    // @group focus
    // @visibility external
    //<
    // Some comments on manual assignment of tabIndex:
    // - useful for inserting into native tab order:
    //   - setting tabIndex to 0 to allow an ISC widget to be inserted into the native, automatic
    //     tab order of a series of native elements which surround it and which have no tabIndex
    //     assigned (where the ISC widget would be drawn either relpos or via Canvas.drawContext)
    //   - setting explicit tabIndex to allow an ISC widget to be inserted into a series of
    //     native elements with explicit tab indices
    //   - NOTE: with both of the above use cases, if a compound widget is inserted, all
    //     focuseable children will need an explicit tabIndex.  In some cases this works
    //     automatically, eg, in a ListGrid, the body and header receive the same tabIndex by
    //     default
    //  - Cannot be used to slot a widget into the middle of the ISC auto-assigned tab loop,
    //    as we enforce the TAB_INDEX_FLOOR upper limit on manually assigned tabindices

    
    _useNativeTabIndex:(isc.Browser.isIE && isc.Browser.version >= 5) || isc.Browser.isSafari ||
                        (isc.Browser.isMoz && isc.Browser.geckoVersion >= 20051111),

    
    _useFocusProxy:(isc.Browser.isMoz && isc.Browser.geckoVersion < 20051111)
                   || isc.Browser.isOpera,


    //> @attr   canvas.accessKey (string : null : IRWA)
    // If specified this governs the accessKey for the widget.
    // This should be set to a character - when a user hits Alt+[accessKey], or in Mozilla
    // Firefox 2.0 and above, Shift+Alt+[accessKey], focus will be given to the widget in
    // question.
    // @group focus
    // @visibility external
    //<

    // Context Menu
	// --------------------------------------------------------------------------------------------

	//>	@attr	canvas.contextMenu		(Menu : null : IRWA)
	// Context menu to show for this object, an instance of the Menu widget.
	// <P>
	// Note: if +link{canvas.destroy()} is called on a canvas, any specified context menu is
	// not automatically destroyed as well. This is in contrast to +link{MenuButton}s which
	// automatically destroy their specified +link{MenuButton.menu} by default. The behavior
	// is intentional as context menus are commonly reused across components.
	//		@group	cues
    //  @see canvas.showContextMenu()
    // @visibility external
    // @example contextMenus
	//<

	//>	@attr	canvas.contextMenuProperties		(object : object : IRW)
    // Default properties for automatically generated context menus
    //<
    
	contextMenuProperties:{
		autoDraw:false,
		width:200,
		showIcons:true
	},

	//> @attr canvas.menuConstructor (SCClassName : "Menu" : IR)
	//  Default class used to construct menus created by this component, including context menus.
	//
	// @group	cues
    // @see canvas.showContextMenu()
    // @visibility external
	//<
	menuConstructor: "Menu",

    //>CornerClips
	// -----------------------------------------------------------------------------------------

    //> @attr canvas.clipCorners    (boolean : false : [IR])
    // Whether to clip corners
    // @visibility cornerClips
    //<
    //clipCorners:false,

    //> @attr canvas.clippedCorners   (Array : ["TL", "TR", "BL", "BR"] : [IR])
    // List of corners that should be clipped
    // @visibility cornerClips
    //<
	clippedCorners:["TL","TR","BL","BR"],

    //> @attr canvas.noCornerClipImages  (boolean : false : [IR])
    // For development purposes, create corner clips without requiring images.  Only works for
    // corners where width and height are equal.
    // <P>
    // Highly experimental and IE specific.
    //
    // @visibility cornerClips
    //<
	//noCornerClipImages:false,

    //> @attr canvas.cornerClipColor     (CSSColor : "FFFFFF" : [IR])
    // HEX color code (WITHOUT #) to match the background.
    //
    // @visibility cornerClips
    //<
	cornerClipColor:"FFFFFF",

    //> @attr canvas.cornerClipImage   (SCImgURL : "[SKIN]roundcorner.gif" : [IR])
    // Base name of image to use for corner clipping images.
    // <P>
    // The full name of each corner image is (base + color + corner name), eg,
    // "roundcorner_FFFFFF_TL.gif"
    //
    // @visibility cornerClips
    //<
	cornerClipImage:"[SKIN]corner.gif",

    //> @attr canvas.cornerClipSize        (number of pixels : 10 : [IR])
    // Size in pixels for corner clips
    // @visibility cornerClips
    //<
	cornerClipSize:10,

    //> @attr canvas.cornerClipWidth       (number of pixels : null : [IR])
    // Width in pixels for corner clips.  Defaults to cornerClipSize when unset.
    // @visibility cornerClips
    //<

    //> @attr canvas.cornerClipHeight      (number of pixels : null : [IR])
    // Height in pixels for corner clips.  Defaults to cornerClipSize when unset.
    // @visibility cornerClips
    //<

	//_cornerClips:null,	// refs to the generated corner cap elements

	_cornerProperties:{
		_generated:true,
		overflow:"hidden",
        
		_redrawWithMaster:false,
		_resizeWithMaster:false,
		autoDraw:false,
		skinImgDir:"images/corners/",

		// scroll cornercap contents to appropriate position after drawing
		// (should only apply to no-image corners)
		draw : function () {
			this.Super("draw",arguments);
            
		}
	},

    

    //<CornerClips

	// --------------------------------------------------------------------------------------------
	//>	@attr canvas.prompt (HTMLString : null : IRW)
	// Prompt displayed in hover canvas if +link{canvas.showHover,showHover} is true.
    // @visibility external
	// @group	hovers
    // @example customHovers
	//<

	// Drag and Drop
	// --------------------------------------------------------------------------------------------

    //>	@attr	canvas.canDrag		(Boolean : false : IRWA)
    //      Indicates whether this widget can initiate custom drag-and-drop operations (other than
    //      reposition or resize). Normally canDragReposition or canDragResize would be used
    //      instead of this property.
    //      Note: this property may be manipulated by higher-level dragging semantics.
    //  @visibility external
    //  @group  dragdrop
    //  @example dragCreate
    //<

    dragOutlineStyle:"dragOutline",

    //>	@attr	canvas.dragStartDistance		(number : 5 : IRWA)
    //
	// Number of pixels the cursor needs to move before the EventHandler starts a drag operation.
    //
	// @group dragdrop
    // @visibility external
	//<
	dragStartDistance:5,

    //>DragScrolling
    //>	@attr	Canvas.canDragScroll (Boolean : true : IRWA)
    //      If this Canvas is canAcceptDrop:true, when the user drags a droppable widget over
    //      an edge of the widget, should we scroll to show the rest of the widget's content?
    //      Returned from canvas.shouldDragScroll() if there are scrollbars.
    //      @visibility external
    //      @see    shouldDragScroll()
    //      @group  dragging
    //<
    canDragScroll : true,

    //>	@attr	canvas.dragScrollDelay    (number : 100 : IRWA)
    //      If this widget supports drag-scrolling, This property specifies how many ms the
    //      user must hover over the drag-scroll threshold before scrolling begins.
    //      @visibility external
    //      @group  dragging
    //<
    dragScrollDelay:100,

    
    //>	@attr	canvas.dragScrollThreshold   (measure : "10%" : IRWA)
    //      If this widget allows drag-scrolling, the dragScrollThreshold is the distance from
    //      the edge of the widget viewport that the user must drag-hover to be in the
    //      scrolling area.  This can be specified as a percentage value like "10%" or an
    //      absolute pixel value.
    //      @visibility internal
    //      @group  dragging
    //<
    dragScrollThreshold:"10%",

    //>	@attr	canvas.minDragScrollIncrement (measure : 1 : IRWA)
    //      If this widget allows drag-scrolling, the rate at which the widget will be scrolled
    //      while the user drag-hovers close to the edge of the widget is determined by how
    //      far the mouse pointer is from the edge.
    //      We provide 2 properties to control this:<br>
    //      - minDragScrollIncrement denotes what size increments will be used to scroll the
    //        widget while the pointer is exactly 1*this.dragScrollThreshold from the edge of
    //        the widget<br>
    //      - maxDragScrollIncrement denotes what size increments will be used to scroll the
    //        widget while the pointer is exactly over the edge of the widget<br>
    //      Each of these properties can be specified as an absolute pixel value to scroll, or
    //      a percentage of the scrollSize of the widget.
    //      @visibility internal
    //      @group  dragging
    //<
    minDragScrollIncrement:1,

    //>	@attr	canvas.maxDragScrollIncrement (measure : "5%" : IRWA)
    //      If this widget allows drag-scrolling, the rate at which the widget will be scrolled
    //      while the user drag-hovers close to the edge of the widget is determined by how
    //      far the mouse pointer is from the edge.
    //      We provide 2 properties to control this:<br>
    //      - minDragScrollIncrement denotes what size increments will be used to scroll the
    //        widget while the pointer is exactly 1*this.dragScrollThreshold from the edge of
    //        the widget<br>
    //      - maxDragScrollIncrement denotes what size increments will be used to scroll the
    //        widget while the pointer is exactly over the edge of the widget<br>
    //      Each of these properties can be specified as an absolute pixel value to scroll, or
    //      a percentage of the scrollSize of the widget.
    //      @visibility internal
    //      @group  dragging
    //<
    maxDragScrollIncrement:"5%",
    //<DragScrolling


    //>	@attr canvas.dragIntersectStyle (DragIntersectStyle : "mouse" : IRWA)
    // This indicates how the system will test for droppable targets: either by intersection
    // with the mouse or intersection with the rectangle of the dragMoveTarget.
    // @group dragdrop
    // @visibility external
    //<
	dragIntersectStyle : isc.EventHandler.INTERSECT_WITH_MOUSE,

    //>	@attr	canvas.canDragReposition	(Boolean : false : IRWA)
    //      Indicates whether this widget can be moved by a user of your application by simply
    //      dragging with the mouse.
    //  @visibility external
    //  @group  dragdrop
    //  @example dragEffects
    //<

    //>	@attr	canvas.dragRepositionCursor	(Cursor : isc.Canvas.MOVE : IRWA)
    // Cursor to switch to if the mouse is over a widget that is drag repositionable.
    //  @visibility external
    //  @group  dragdrop
    //<
	dragRepositionCursor:isc.Canvas.MOVE,

    //>	@attr	canvas.canDragResize		(Boolean : false : IRWA)
    //      Indicates whether this widget can be resized by dragging on the edges and/or corners of
    //      the widget with the mouse.
    //  @visibility external
    //  @group  dragdrop
    //  @example dragResize
    //<

    //> @type EdgeName
    // An edge or corner of a rectange, or it's center.  Used in APIs such as
    // +link{canvas.resizeFrom} and +link{canvas.getEventEdge()}.
    // @value "T" top edge
    // @value "B" bottom edge
    // @value "L" left edge
    // @value "R" right edge
    // @value "TL" top-left corner
    // @value "TR" top-right corner
    // @value "BL" bottom-left corner
    // @value "BR" bottom-right corner
    // @value "C" center
    //
    // @visibility external
    //<

	//>	@attr canvas.resizeFrom (Array of EdgeName : null : IRWA)
    // When drag resizing is enabled via +link{canDragResize}, restricts resizes to only
    // certain edges or corners.
    // <P>
    // The default of null indicates the widget can be resized from any corner or edge (if
    // <code>canDragResize</code> is true).
    // <P>
    // To restrict resizing to only certain corners, set <code>resizeFrom</code> to an Array of
    // +link{EdgeName}s.
    //
	// @group dragdrop
    // @example dragResize
    // @visibility external
	//<

    //>	@attr	canvas.dragScrollType		(string : "any" : IRWA)
    //      If this canvas is a dragTarget and this property is set to "parentsOnly", then only its
    //      parent chain should be checked for possible scrollers.
    //  @group  dragdrop
    //<
    dragScrollType:"any",

    //>	@attr	canvas.dragScrollDirection (string : null : IRWA)
    // If this canvas is a dragTarget, this property may be set to limit which direction the
    // parent / other scrollable widget is scrolled on drag-over.<br>
    // Options are "vertical" or "horizontal".
    //  @group  dragdrop
    //<
    //dragScrollDirection:null,

    //> @attr canvas.canHover (boolean : null : IRW)
    // Will this Canvas fire hover events when the user hovers over it, or one of its children?
    //  @group hovers
    //  @visibility external
    //  @see canvas.showHover
    //  @see canvas.hover()
    //<
    //canHover:null,

    //> @attr canvas.hoverDelay (number : 300 : IRW)
    // If <code>this.canHover</code> is true, how long should the mouse be kept over this
    // widget before the hover event is fired
    //  @group hovers
    //  @visibility external
    //  @see canvas.canHover
    //  @see canvas.hover()
    //<
    hoverDelay:300,

    //> @attr canvas.showHover (Boolean : true : IRW)
    // If <code>this.canHover</code> is true, should we show the global hover canvas by default
    // when the user hovers over this canvas?
    //  @group hovers
    //  @visibility external
    //  @see canvas.getHoverHTML()
    //<
    showHover:true,

    //> @attr canvas.hoverWidth (int : null : IRW)
    // If +link{canvas.showHover,this.showHover} is true, this property can be used to customize the
    // width of the hover canvas shown.
    //  @group hovers
    //  @visibility external
    //  @see canvas.showHover
    //  @example customHovers
    //<
    //hoverWidth:null,

    //> @attr canvas.hoverHeight (int : null : IRW)
    // If <code>this.showHover</code> is true, this property can be used to customize the
    // height of the hover canvas shown.
    //  @group hovers
    //  @visibility external
    //  @see canvas.showHover
    //<
    //hoverHeight:null,

    //> @attr canvas.hoverAlign (Alignment : null : IRW)
    // If <code>this.showHover</code> is true, this property can be used to customize the
    // alignment of content in the hover canvas.
    //  @group hovers
    //  @visibility external
    //  @see canvas.showHover
    //<
    //hoverAlign:null,

    //> @attr canvas.hoverVAlign (VerticalAlignment : null : IRW)
    // If <code>this.showHover</code> is true, this property can be used to customize the
    // vertical alignment of content in the hover canvas.
    //  @group hovers
    //  @visibility external
    //  @see canvas.showHover
    //<
    //hoverVAlign:null,

    //> @attr canvas.hoverWrap (boolean : null : IRW)
    // If <code>this.showHover</code> is true, this property can be used to customize the
    // whether content in the hover canvas is displayed in a single line, or wraps.
    //  @group hovers
    //  @visibility external
    //  @see canvas.showHover
    //<
    //hoverWrap:null,

    //> @attr canvas.hoverStyle (CSSStyleName : null : IRW)
    // If <code>this.showHover</code> is true, this property can be used to specify the
    // css style to apply to the hover canvas.
    //  @group hovers
    //  @visibility external
    //  @see canvas.showHover
    //  @example customHovers
    //<
    //hoverStyle:null,

    //> @attr canvas.hoverOpacity (number : null : IRW)
    // If <code>this.showHover</code> is true, should the hover canvas be shown with opacity
    // other than 100?
    // @visibility external
    // @group hovers
    // @see canvas.showHover
    //  @example customHovers
    //<
    //hoverOpacity:null,

    //> @attr canvas.hoverMoveWithMouse (boolean : null : IRW)
    // If <code>this.showHover</code> is true, should this widget's hover canvas be moved with
    // the mouse while visible?
    // @visibility external
    // @group hovers
    // @see canvas.showHover
    //<

    //> @attr canvas.hoverAutoDestroy (boolean : null : IRW)
    // If <code>this.showHover</code> is true and +link{getHoverComponent()} is implemented, should
    // the hoverCanvas returned from it be automatically destroyed when it is hidden?
    // <P>
    // The default of null indicates that the component <b>will</b> be automatically
    // destroyed.  Set to false to prevent this.
    //
    // @visibility external
    // @group hovers
    // @see canvas.showHover
    //<

    //>	@attr	canvas.edgeMarginSize		(number : 5 : IRWA)
	// How far into the edge of an object do we consider the "edge" for drag resize purposes?
	// @group dragdrop
    // @example dragResize
    // @visibility external
	//<
	edgeMarginSize:5,

	//>	@attr	canvas.edgeCursorMap		(object : {...} : IRWA)
	// Cursor to use when over each edge of a Canvas that is drag resizable.
    // <P>
    // To disable drag resize cursors, set the edgeCursorMap property to null.
    //
    //  @see resizeFrom
    //  @visibility external
	//  @group	dragdrop
	//<
    // NOTE: cursor change is actually accomplished in isc.EventHandler.handleMouseMove()
	edgeCursorMap : {
			"T":"n-resize",
			"L":"w-resize",
			"B":"s-resize",
			"R":"e-resize",
			"TL":"nw-resize",
			"TR":"ne-resize",
			"BL":"sw-resize",
			"BR":"se-resize"
	},

	//>	@attr canvas.keepInParentRect (Boolean or Array of Integer : null : IRWA)
	// Constrains drag-resizing and drag-repositioning of this canvas to either the rect of its
    // parent (if set to true) or an arbitrary rect based its parent (if set to a
    // [Left,Top,Width,Height] rect array).  In the latter mode you may use negative offsets
    // for left/top and a width/height greater than the visible or scroll width of the parent
    // to allow positioning beyond the confines of the parent.
    // <p>
    // If this canvas has no parent, constrains dragging to within the browser window.
    // <p>
	// Affects target and outline dragAppearance, not tracker.
    // <p>
    // Note: keepInParentRect affects only user drag interactions, not programmatic moves.
    // <p>
    // <u>Example use cases:</u><br>
    // <code>keepInParentRect: true</code> - confine to parent<br>
    // <code>keepInParentRect: [0, 0, 500, 500]</code> - confine to top left 500x500 region within parent<br>
    // <code>keepInParentRect: [0, 0, 10000, 10000]</code> - in combination with
    // overflow: "auto", confine to parent, but allow moving off the right and bottom of the
    // parent to force scrolling (and hence enlarge the scrollWidth of the parent).
    //
    // @group dragdrop
    // @visibility external
    // @example dragCreate
	//<

	//>	@attr	canvas.dragAppearance		(DragAppearance : isc.EventHandler.OUTLINE : IRWA)
    // Visual appearance to show when the object is being dragged. May be overridden for
    // dragResize or dragReposition events via +link{canvas.dragResizeAppearance}
    // and +link{canvas.dragRepositionAppearance}.
    //  @visibility external
	//  @group	dragdrop
	//<
	dragAppearance:isc.EventHandler.OUTLINE,

	//>	@attr	canvas.dragResizeAppearance		(DragAppearance : null : IRWA)
    // If +link{canvas.canDragResize} is true, this attributes specifies the visual appearance
    // to show during drag resize. If unset +link{canvas.dragAppearance} will be used.
    //  @visibility external
	//  @group	dragdrop
	//<

	//>	@attr	canvas.dragRepositionAppearance		(DragAppearance : null : IRWA)
    // If +link{canvas.canDragReposition} is true, this attributes specifies the visual appearance
    // to show during drag reposition. If unset +link{canvas.dragAppearance} will be used.
    //  @visibility external
	//  @group	dragdrop
	//<

	getDragAppearance : function (dragOperation) {
	    if (dragOperation == isc.EH.DRAG_RESIZE && this.dragResizeAppearance != null)
	        return this.dragResizeAppearance;
	    if (dragOperation == isc.EH.DRAG_REPOSITION && this.dragRepositionAppearance != null)
	        return this.dragRepositionAppearance;
	    return this.dragAppearance;
	},

    //>	@attr	canvas.dragType		(string : null : IRWA)
    //      The "type" of thing given as a string that can be dragged from this widget. If
    //      specified, this will be matched up with the dropTypes of droppable widgets as detailed
    //      in the dropTypes property.
    //  @visibility external
	//  @group	dragdrop
    // @see canvas.dropTypes
	//<

	//>	@attr	canvas.dragTarget		(Canvas | String: null : IRWA)
    //      A different widget that should be actually dragged when dragging initiates on this
    //      widget. One example of this is to have a child widget that drags its parent, as with a
    //      drag box. Because the parent automatically repositions its children, setting the drag
    //      target of the child to the parent and then dragging the child will result in both
    //      widgets being moved.<br>
    //      Valid dragTarget values are:<br>
    //      - <code>null</code> (default) [this widget is its own drag target]<br>
    //      - Pointer to another widget, or widget ID<br>
    //      - <code>"parent"</code> drag target is this widget's
    //         +link{Canvas.parentElement, parentElement}<br>
    //      - <code>"top"</code> drag target is this widget's
    //         +link{Canvas.topElement, topElement}<br>
    //  @see EventHandler.getDragTarget()
    //  @visibility external
	//  @group	dragdrop
	//<

	//>	@attr canvas.showDragShadow   (boolean : null : IRWA)
    // When this widget is dragged, if its dragAppearance is <code>"target"</code>, should
    // we show a shadow behind the canvas during the drag.
    //
	// @group dragdrop
    // @visibility external
    // @example dragEffects
	//<

	//>	@attr canvas.dragOpacity  (number : null : IRWA)
    // If this widget has dragAppearance <code>"target"</code>, this value specifies the
    // opacity to render the target while it is being dragged. A null value implies we do
    // not modify the opacity.
    //
	// @group dragdrop
    // @visibility external
    // @example dragEffects
	//<

    //>	@attr	canvas.canDrop		(Boolean : false : IRWA)
    //      Indicates that this object can be dropped on top of other widgets. Only valid if
    //      canDrag or canDragReposition is true.
	//  @group	dragdrop
    //  @visibility external
    //  @example dragCreate
	//<

    //>	@attr	canvas.canAcceptDrop		(Boolean : false : IRWA)
    //      Indicates that this object can receive dropped widgets (i.e. other widgets can be
    //      dropped on top of it).
	//  @group	dragdrop
    //  @visibility external
    //  @example dragCreate
	//<


    //>	@attr	canvas.canDropBefore		(boolean : null : IRWA)
    // When explicitly set to false, disallows drop before this member in the Layout.
    //
	// @group	layoutMember
    // @see Layout
    // @visibility external
	//<

    //>	@attr	canvas.dropTypes		(string | array of strings : isc.Canvas.ANYTHING : IRWA)
    //
    // The "type" of thing(s) that can be dropped on this widget specified as a string or an
    // array of strings (indicating multiple types). Leave this with the value null to indicate
    // that this widget can accept anything dropped on it from the page.
    //
	// @group dragdrop
    //
    // @see Canvas.willAcceptDrop()
    // @see Canvas.dragType
    //
    // @visibility external
	//<
	dropTypes:isc.Canvas.ANYTHING,

	//>	@attr	canvas.mouseStillDownInitialDelay		(number : 400 : IRWA)
	// Amount of time (in msec) before mouseStillDown events start to be fired for this object.
    //
	// @group	events
    // @visibility external
	//<
    
	mouseStillDownInitialDelay:400,

	//>	@attr	canvas.mouseStillDownDelay		(number : 100 : IRWA)
	// Amount of time (in msec) between 'mouseStillDown' events for this object
    //
    // @visibility external
	// @group events
	//<
	mouseStillDownDelay:100,

	//>	@attr	canvas.doubleClickDelay		(number : 250 : IRWA)
	// Amount of time (in msec) between which two clicks are considered a single click
	// @group	events
    // @visibility external
	//<
	doubleClickDelay:250,

    //> @attr   canvas.noDoubleClicks   (boolean : null : IRWA)
    // If true, this canvas will receive all mouse-clicks as single click events rather than
    // doubleClicks.
    // @group events
    // @visibility external
    //<
    //noDoubleClicks:false,

	// --------------------------------------------------------------------------------------------
    // variable name to use with refreshFromServer / replaceFromServer
    refreshVariable : "refresh",

	// --------------------------------------------------------------------------------------------
	
    
    //>Moz
    _useMozScrollbarsNone : (isc.Browser.isMoz &&
                               (!isc.Browser.isUnix || isc.Browser.geckoVersion > 20031007)),
    _canScrollHidden:isc.Browser.isMoz && isc.Browser.version >= 18,
    //<Moz

    // Whether to create a clipDiv/contentDiv pair vs a single DIV.
    // NOTE: we have a function shouldCreateClipDiv() which tells us under what circumstances we
    // really need to use a clipDiv.

    // Writing two DIVs has a performance impact, so specific subclasses which don't rely on all of
    // Canvas' features working correctly might want to override this in order to have a lighter
    // weight DOM representation.  NOTE: the strategy of using nested DIVs can actually be used in
    // any browser, in fact, it works in IE and corrects some bugs, although it is noticeably slower
    useClipDiv: isc.Browser.useClipDiv,
    //useClipDiv: false,

    
    manageChildOverflow:true,

	//>	@attr Canvas.useBackMask (Boolean : false : IRWA)
	// In earlier versions of Internet Explorer (pre IE9), a native limitation
	// exists  whereby if HTML elements are
	// overlapping on the page, certain elements can appear to "burn through"
	// elements in the same position with a higher z-index.
	// Specific cases in which this have been observed include Applets,
	// &lt;IFRAME&gt; elements, and for
	// older versions of IE, native &lt;SELECT&gt; items.
	// <P>
	// The backMask is a workaround for this issue. If <code>useBackMask</code> is
	// set to <code>true</code>, the component will render an empty &lt;IFRAME&gt; element
	// behind the canvas, which prevents this effect in all known cases.
	// <P>
	// Has no effect in other browsers.
	// @visibility external
	//<

    //> @attr canvas.useEventParts  (boolean : false : IRWA)
    // If true, when this widget receives events, it will check whether the native DOM element
    // that received the event has been marked as a special "part" of this widget, and if so
    // fire the appropriate part events.<br>
    // Elements written into this canvas can be marked as 'parts' by setting the 'eventpart'
    // attribute to the name of the part type. The events fired are then based upon this
    // property. For example an element with eventpart set to "rect" would cause this widget
    // to fire "rectMouseOver", "rectMouseOut" handlers. The element, and a unique ID for the
    // part would be passed into those custom handlers
    // @visibility eventParts
    //<
    

    // _lastOverPart - if we're handling partwise events, this is used to track what the user
    // was last over within this canvas.
    // Format: {part:[parttype], element:[element], ID:[ID]}
    _lastOverPart:{},

    //> @type PercentBoxModel
    // Determines sizing model when sizing / positioning a canvas relative to its
    // +link{canvas.percentBox,percentBox}.
    // @value "visible" use coordinates relative to the
    // {+link{canvas.getVisibleHeight()},visibleHeight} and width of the other canvas
    // @value "viewport" use coordinates relative to the
    // {+link{canvas.getViewportHeight()},viewportHeight} and width of the other canvas
    // @visibility external
    //<

    //> @attr canvas.percentSource (Canvas : null : IRWA)
    // If this canvas has its size specified as a percentage, this property allows the user to
    // explicitly designate another canvas upon which sizing will be based.
    // <P>
    // If unset percentage sizing is based on<br>
    // - the +link{canvas.masterElement,masterElement} if there is one and
    //   +link{canvas.snapTo,snapTo} is set,<br>
    // - otherwise on the amount of space available in this widget's parentElement, if this is
    //   a child of some other widget<br>
    // - otherwise the page size.
    // @group sizing
    // @see attr:canvas.percentBox
    // @visibility external
    //<

    //> @attr canvas.percentBox (PercentBoxModel : "visible" : IRA)
    // Governs the model to be used when sizing canvases with percentage width or height, or
    // positioning widgets with a specified +link{canvas.snapTo,snapTo}.
    // <P>
    // Only affects widgets with a a specified +link{canvas.percentSource,percentSource}, or
    // widgets that have +link{canvas.snapTo} set and are peers of some
    // +link{canvas.masterElement,other canvas}.
    // <P>
    // Determines whether the coordinates used for sizing (for percentage sized widgets) and
    // positioning (if <code>snapTo</code> is set) should be relative to the visible size or the
    // viewport size of the percentSource or masterElement widget.
    // @group sizing
    // @visibility external
    //<
    percentBox:"visible",
    _$viewport:"viewport",

	//> @attr canvas.snapTo (String : null : IRW)
	// Position this widget such that it is aligned with ("snapped to") an edge of its
    // +link{canvas.masterElement,masterElement} (if specified), or its
    // +link{canvas.parentElement,parentElement}.
    // <P>
    // Note that this property also impacts the sizing of this widget. If this widgets size
    // is specified as a percent value, and has no explicit
    // +link{Canvas.percentSource}, sizing will be calculated based on the size of the
    // masterElement when snapTo is set.
    // <P>
	// Possible values: BR, BL, TR, TL, R, L, B, T, C where B=Bottom, T=Top, L=Left, R=right
    // and C=center
    // <P>
    // Standard snapTo behavior will attach the outer edge of the widget to the parent or master
    // element - for example setting <code>snapTo</code> to <code>"B"</code> would align the
    // bottom edge of this component with the bottom edge of the master or parent element
    // (and center this component horizontally over its master or parent element).
    // +link{Canvas.snapEdge} can be specified to change this behavior allowing the developer
    // to, for example, align the top edge of this component with the bottom edge of its
    // masterElement.
    // <P>
    // +link{Canvas.snapOffsetLeft} and +link{Canvas.snapOffsetTop} may also be specified to
    // offset the element from exact snapTo alignment.
    // @group sizing
    // @see canvas.snapEdge
    // @see canvas.percentBox
	// @visibility external
	//<

	//> @attr canvas.snapEdge (String : null : IRW)
    // If +link{canvas.snapTo,snapTo} is defined to this widget, this property can be used to
    // define which edge of this widget should be snapped to an edge of the master or parent
    // element.
    // <P>
    // If unspecified the, default snapTo behavior is set up to align the "snapTo" edge of this
    // widget with the snapTo edge of the master or parent.
    //
    // @group sizing
    // @see canvas.snapTo
	// @visibility external
	//<

    //> @attr canvas.snapOffsetLeft (integer : null : IRW)
    // If +link{canvas.snapTo,snapTo} is defined for this widget, this property can be used to
    // specify an offset in px or percentage for the left coordinate of this widget.
    // <P>
    // For example if <code>snapTo</code> is specified as <code>"L"</code> and
    // <code>snapOffsetLeft</code> is set to 6, this widget will be rendered 6px inside the left
    // edge of its parent or master element. Alternatively if <code>snapTo</code> was set
    // to <code>"R"</code>, a <code>snapOffsetLeft</code> value of -6 would cause the
    // component to be rendered 6px inside the right edge of its parent or masterElement.
    // @group sizing
    // @see canvas.snapTo
    // @visibility external
    //<

    //> @attr canvas.snapOffsetTop (integer : null : IRW)
    // If +link{canvas.snapTo,snapTo} is defined for this widget, this property can be used to
    // specify an offset in px or percentage for the top coordinate of this widget.
    // <P>
    // For example if <code>snapTo</code> is specified as <code>"T"</code> and
    // <code>snapOffsetTop</code> is set to 6, this widget will be rendered 6px below the top
    // edge of its parent or master element. . Alternatively if <code>snapTo</code> was set
    // to <code>"B"</code>, a <code>snapOffsetTop</code> value of -6 would cause the
    // component to be rendered 6px inside the bottom edge of its parent or masterElement.
    // @group sizing
    // @see canvas.snapTo
    // @visibility external
    //<

    //> @attr canvas.snapToGrid (boolean : null : IRW)
	// Causes this canvas to snap to its parent's grid when dragging.
	// @visibility external
	// @see	Canvas.childrenSnapToGrid
	// @group dragdrop
	//<

	//> @attr canvas.snapResizeToGrid (boolean : null : IRW)
	// Causes this canvas to snap to its parent's grid when resizing.
	// Note that this value defaults to the Canvas's
	// +link{Canvas.snapToGrid, snapToGrid} value if undefined.
	// @visibility external
	// @group dragdrop
	//<

	//> @attr canvas.childrenSnapToGrid (boolean : null : IRW)
	// If true, causes this canvas's children to snap to its grid when dragging.
	// This behavior can be overridden on a per-child basis by setting the
	// +link{Canvas.snapToGrid, snapToGrid} value on the child.
	// @visibility external
	// @group dragdrop
	//<

	//> @attr canvas.childrenSnapResizeToGrid (boolean : null : IRW)
	// If true, causes this canvas's children to snap to its grid when resizing.
	// This behavior can be overridden on a per-child basis by setting the
	// +link{Canvas.snapToGrid, snapToGrid} or
	// +link{Canvas.snapResizeToGrid, snapResizeToGrid} value on the child.
	// @visibility external
	// @group dragdrop
	//<

	//> @attr canvas.snapHGap (number : 20 : IRW)
	// The horizontal grid size to use, in pixels, when snap-to-grid is enabled.
	// @visibility external
	// @group dragdrop
	// @see	Canvas.snapToGrid
	// @see	Canvas.snapResizeToGrid
	// @see	Canvas.childrenSnapToGrid
	// @see	Canvas.childrenSnapResizeToGrid
	//<
   snapHGap: 20,

	//> @attr canvas.snapVGap (number : 20 : IRW)
	// The vertical grid size to use, in pixels, when snap-to-grid is enabled.
	// @visibility external
	// @group dragdrop
	// @see	Canvas.snapToGrid
	// @see	Canvas.snapResizeToGrid
	// @see	Canvas.childrenSnapToGrid
	// @see	Canvas.childrenSnapResizeToGrid
	//<
   snapVGap: 20,

	//> @attr canvas.snapHDirection (string : "after" : IRW)
	// The horizontal snap direction.
	// Set this value to "before" to snap to the nearest gridpoint to the left;
	// set it to "after" to snap to the nearest gridpoint to the right; and set
	// it to "nearest" to snap to the nearest gridpoint in either direction.
	// @visibility external
	// @group dragdrop
	// @see	Canvas.snapToGrid
	// @see	Canvas.snapResizeToGrid
	// @see	Canvas.childrenSnapToGrid
	// @see	Canvas.childrenSnapResizeToGrid
	//<
   snapHDirection: isc.Canvas.AFTER,

	//> @attr canvas.snapVDirection (string : "after" : IRW)
	// The vertical snap direction.
	// Set this value to "before" to snap to the nearest gridpoint above;
	// set it to "after" to snap to the nearest gridpoint below; and set
	// it to "nearest" to snap to the nearest gridpoint in either direction.
	// @visibility external
	// @group dragdrop
	// @see	Canvas.snapToGrid
	// @see	Canvas.snapResizeToGrid
	// @see	Canvas.childrenSnapToGrid
	// @see	Canvas.childrenSnapResizeToGrid
	//<
   snapVDirection: isc.Canvas.AFTER,

	//> @attr canvas.snapAxis (string : "both" : IRW)
	// Describes which axes to apply snap-to-grid to.
	// Valid values are "horizontal", "vertical" or "both".
	// @visibility external
	// @group dragdrop
	// @see	Canvas.snapToGrid
	// @see	Canvas.snapResizeToGrid
	// @see	Canvas.childrenSnapToGrid
	// @see	Canvas.childrenSnapResizeToGrid
	//<
   snapAxis: isc.Canvas.BOTH,

	//> @attr canvas.snapOnDrop (Boolean : true : IRWA)
	// When this canvas is dropped onto an object supporting snap-to-grid, should it snap
	// to the grid (true, the default) or just drop wherever the mouse is (false).
	// @visibility external
	// @group dragdrop
	// @see	Canvas.snapToGrid
	// @see	Canvas.shouldSnapOnDrop()
	//<
   snapOnDrop: true
});


isc.Canvas.addMethods({

// basic terms
_$resize: "resize",
_$draw: "draw",
_$hidden: "hidden",
_$redraw: "redraw",
_$undefined: "undefined",

// various log and stat categories
_$draws: "draws",
_$drawing: "drawing",
_$redraws: "redraws",
_$autoDraw: "autoDraw",

// insertAdjacentHTML positions

_$beforeBegin : "beforeBegin",
_$afterBegin: "afterBegin",
_$beforeEnd: "beforeEnd",
_$afterEnd: "afterEnd",

// characters
_$rightAngle : ">",
_$singleQuote : "'",
_$doubleQuote : '"',

// Initialization
// --------------------------------------------------------------------------------------------

//>	@method	canvas.init()	(A)
//
// This method performs some basic initialization common to all UI components.  To do custom UI
// component initialization, you should generally override +link{Canvas.initWidget()}.  This
// method does the following, in order:
// <ul>
// <li>Sets up a global reference to this instance as described in +link{Canvas.ID}.
// <li>Ensures certain numeric properties have numeric values (e.g. width, height, padding,
// margin)
// <li>Ensures +link{canvas.children} and +link{canvas.peers} are Arrays.
// <li>Calls +link{Canvas.initWidget()}
// <li>Creates +link{showEdges,edges} and +link{showShadow,shadow}, if so configured.
// <li>Calls +link{Canvas.draw()} if +link{Canvas.autoDraw} is set on instance or globally.
// </ul>
// Unless you're in an advanced scenario where you need to inject code before the above
// logic executes, place your initialization logic in initWidget() rather than init().  If you
// do decided to override this method, you must call the superclass implementation like so:
// <pre>
//    this.Super("init", arguments);
// </pre>
//
// @param	[arguments 0-N] (any)	All arguments initially passed to +link{Class.create()}
//
// @visibility external
//<
init : function (A,B,C,D,E,F,G,H,I,J,K,L,M) {

    //this.addProperties(isc.getRegisteredInstanceProperties(this.ID));
    if (isc._traceMarkers) arguments.__this = this;

    // Perform a one time check to see if we're creating any canvases in the <head>
    // If so log a warning.
    // We don't support drawing widgets outside the body at all.
    // Creating widgets without drawing outside the body is unreliable - in some
    // cases, even having autoDraw set to false, we will attempt to write out HTML on widget
    // init to (EG) derive styling information.
    
    if (!isc.Canvas._outsideBodyCheck) {
        if (this.getDocumentBody(true) == null) {
            isc.logWarn("Canvas created in a page outside the BODY tag. This is not supported. " +
                "Isomorphic Software requires the tag to be present and all widgets be created " +
                "and drawn inside it. Canvas details follow:\n" +
                isc.Log.echo(this));
            
        }
        isc.Canvas._outsideBodyCheck = true;
    }

	// get a global ID so we can be called in the global scope
	this.ns.ClassFactory.addGlobalID(this);

	// put this object in the master list of Canvases that have been created so it can be
    // deallocated later
	this._canvasList(true);

    // if position has not been set, default it to relative if htmlElement is set.
    if (this.position == null) {
        this.position = this.htmlElement != null ? isc.Canvas.RELATIVE : isc.Canvas.ABSOLUTE;
    }

    
    if (this.className != null && this.logIsInfoEnabled(this._$styleName)) {
        this.logInfo("'className' property specified. This property has been deprecated in "
                    + "favor of 'styleName' as of SmartClient 5.5.", this._$styleName);
    }
    if (this.styleName != null) {
        // both stylename and className are set
        if (this.className != null) {
            // Either .styleName and .className were explicitly passed in, or just one of the
            // properties was.
            // - respect the styleName passed in, if present, otherwise the className passed in
            var proto = this.getPrototype(),
                explicitStyle = (this.styleName != proto.styleName),
                explicitClassName = (this.className != proto.className);
            if (explicitStyle) this.className = this.styleName;
            else if (explicitClassName) this.styleName = this.className;
            // Both set on the prototype - respect the older .className attribute name
            else this.styleName = this.className;

        // .className property unset - just duplicate this.styleName
        } else {
            this.className = this.styleName;
        }
    // className set, but not styleName - copy it across
    } else if (this.className != null) {
        this.styleName = this.className;
    }

    // convenience for square components, only documented for Img/ImgButton
    if (this.size != null) this.height = this.width = this.size;

    // save the current width/height so we can tell if a width/height was
    // explicitly set, and default any dimensions that weren't set. Also, if
    // width or height are numeric strings, parse them to numbers. Since width
    // and height can be strings (e.g. "100%" or "*", they will deserialize as
    // strings even if numeric (e.g. "275"). However, it's more convenient to deal
    // with them as actual numbers in that case.
    this._userWidth = this.width = isc.NumberUtil.parseIfNumeric(this.width);
    this._userHeight = this.height = isc.NumberUtil.parseIfNumeric(this.height);

    if (this.width == null) this.width = this.defaultWidth;
    if (this.height == null) this.height = this.defaultHeight;

    // copy the height property to this._height.  This is what we'll rely upon internally
    // for sizing
    
    this._height = this.height;

    // Ensure margin / padding is a numeric value
    if (isc.isA.String(this.margin)) {
        var margin = parseInt(this.margin);
        if (isc.isA.Number(margin)) this.margin = margin;
        else {
            this.logWarn("Invalid setting for this.margin:" + this.margin +
                         ". This should be a numeric value - ignoring");
            this.margin = null;
        }
    }
    if (isc.isA.String(this.padding)) {
        var padding = parseInt(this.padding);
        if (isc.isA.Number(padding)) this.padding = padding;
        else {
            this.logWarn("Invalid setting for this.padding:" + this.padding +
                         ". This should be set to a numeric value - ignoring");
            this.padding = null;
        }
    }

    // Handle the case where a border was (incorrectly) specified as a number directly
    if (this.border != null && !isc.isA.String(this.border)) {
        this.border = this._convertBorderToString(this.border);
    }
    if (this.borderRadius != null && !isc.isA.String(this.borderRadius)) {
        this.borderRadius = this._convertBorderRadiusToString(this.borderRadius);
    }

    // resolve percentSource (if specified) to a widget, and observe it's inner size changing
    if (this.percentSource) this.setPercentSource(this.percentSource, true);

    // Call moveTo() and resizeTo() to resolve percentage positions and sizes to pixels.  If
    // coords are already numeric this is a no-op.
    // NOTE: if we have a parentElement, our percent size should be a proportion of it's size,
    // which we will only know when our parent draws us, so our parent tells us to
    // resolvePercentageSizes again right when we are about to draw().  However, if we delay
    // resolving this.width til draw:
    // - classes that override draw() won't support percent width automatically
    // - it won't be safe to manipulate this.width until right before draw (eg, for layout)
    //   - if you had to call getWidth(), you could at least get a correct value for widths
    //     that are a percentage of the page
    // For this reason child layout code should generally run from layoutChildren(), right
    // before drawChildren().
    this._canvas_initializing = true; // HACK to allow resized() notifications to be ignored
    this.resizeTo(this.width, this._height, undefined, undefined, "init");
    this.moveTo(this.left, this.top);
    this._canvas_initializing = null;

    // - Normalize children / peers properties into arrays
	if (this.children && !isc.isAn.Array(this.children)) this.children = [this.children];
	if (this.peers && !isc.isAn.Array(this.peers)) this.peers = [this.peers];

    // NOTE: we ensure a unique children Array via instantiateChildren.
    // The usage of declaring children as an instance prototype property is OK so long as the
    // children are specified as objects, not live Canvii, and none of classes involved assume
    // they have a unique copy of any shared subobjects.
    //if (this.children != null && this.children === this._scPrototype.children) {
        //this.logWarn("Detected children array as instance property")
        //this.children = this.children.duplicate();
    //}
    // We have a mechanism to avoid this - we could call 'registerDupProperty' to
    // register children as a special property for duplication (IE shallow cloning) in this
    // case - but we don't currently have a use case where this is required

    // If the deprecated 'enabled' property is set, set the disabled property to match it.
    
    if (this.enabled != this._$unset) {
        this.logWarn("Widget initialized with explicitly specified 'enabled' property. " +
                     "This property has been deprecated - use 'disabled' instead.");
        this.disabled = !this.enabled;
    }
    
    if (this.redrawOnEnable != null) {
        this.logWarn("Widget initialized with deprecated 'redrawOnEnable' - use 'redrawOnDisable' instead.");
        this.redrawOnDisable = this.redrawOnEnable;
    }

	// call initWidget() to give each subclass of canvas a chance to initialize its child
    // structures
	this.initWidget(A,B,C,D,E,F,G,H,I,J,K,L,M);

    //>RoundCorners create a Canvas to show edges (eg rounded corners) around this Canvas
    this._createEdges();
    //<RoundCorners

    // automatically create a drop shadow
    if (this.showShadow) this._createShadow();

    //>CornerClips
    if (this.clipCorners) this._makeCornerClips(); //<CornerClips

    
	if (this.useBackMask && ((isc.Browser.isIE && isc.Browser.minorVersion >= 5.5)
                             || (isc.Canvas.useMozBackMasks && isc.Browser.isMoz))) {
        this.makeBackMask();
    }

    // Show group frame if appropriate
    if (this.isGroup) {
        this.isGroup = null;
        this.setIsGroup(true);
    }

    // Make sure each child and peer knows that it is not to auto-draw.
    // This prevents the children from drawing outside of our context.
	// Do this after initWidget so that if you add a new child it gets the proper setting.
	if (this.children) this.children.setProperty(this._$autoDraw, false);
	if (this.peers) this.peers.setProperty(this._$autoDraw, false);

	// if the canvas has a 'observes' property, set those observations up
	if (this.observes) {

        var item,
            source,
            list = this.observes,
            len = list.length;

		for (var i = 0; i < len; i++) {
			// get the next item in the list
			var item = list[i];
			if (!item) continue;
			// if item.source is a string, treat it as a reference to a global object
			//	and call getGlobalReference() to get the reference to the actual object
			if (isc.isA.String(item.source)) source = this.getGlobalReference(item.source);
			else						 source = item.source;

			// if the source was found, set up the observation
			if (source) {
				this.observe(source, item.message, item.action);
			}
		}
	}

    // create child instances (if necessary) and add them as children
    this._instantiateChildren();

    // if any autoChildren are speicified in the autoChildren array, add them via the
    // addAutoChild() mechanism
    if (this.autoChildren) this.addAutoChildren(this.autoChildren);

    // designated for us by skins and instances to add autoChildren to existing components.
    // Custom components should not use this.
    if (this.addOns) this.addAutoChildren(this.addOns);

    //>!BackCompat 2004.08.05
    if (this._adjacentHandle && !this.drawContext) {
        this.drawContext = { element : this._adjacentHandle };
    } //<!BackCompat

    if (this.htmlElement) {
        var element = this.htmlElement;
        delete this.htmlElement;
        this.setHtmlElement(element);
    }

    // If we have an eventProxy, add a pointer on that widget back to this one - used by EH.
    if (this.eventProxy != null) {
        if (!isc.isA.Canvas(this.eventProxy)) {
            this.logWarn("Canvas ID:'" +this.getID() + "' initialized with bad eventProxy. " +
                         "This property should be set to another Canvas instance. Clearing this property.")
            delete this.eventProxy;
        } else {
            if (this.eventProxy._proxiers == null) this.eventProxy._proxiers = [];
            this.eventProxy._proxiers.add(this);
        }
    }

    // allow initialization of parentElement.
    // NOTE: needs to be done before autoDraw, and should probably be done after all of this
    // Canvii's children have been created
    var parentElement = this.parentElement;
    if (parentElement) {
        this.parentElement = null; // need to wipe this out or addChild with no-op
        if (isc.isA.String(parentElement)) parentElement = window[parentElement];
        //if (parentElement.children.contains(this)) this.logWarn("already contained!");
        parentElement.addChild(this);
    }

    //>!BackCompat 2009.7.7
    // We created and exposed 'autoFetchAsFilter' for the 7.0 release candidate builds.
    // If specified use it to override autoFetchTextMatchStyle
    // databinding /
    if (this.autoFetchAsFilter != null) {
        var aftms = this.autoFetchAsFilter ? "substring" : "exact";
        this.logWarn("This component has autoFetchAsFilter explicitly specified as:" +
                    this.autoFetchAsFilter + ". This attribute is deprecated in favor of " +
                    "this.autoFetchTextMatchStyle. Defaulting autoFetchTextMatchStyle to \"" +
                    aftms + "\" based on this setting.");
        this.autoFetchTextMatchStyle = aftms;
    }
    //<!BackCompat


    // On init(), if we have a specified valuesManager, or a dataPath implying we should
    // attach to one, set that relationship up.
    // This is implemented in dataBoundComponent and will no-op in most cases
    this.initializeValuesManager();

	// panelHeader implementation
	if (this.showPanelHeader == true) {
        if (this.setupPanelHeader) this.setupPanelHeader();
        if (this.refreshPanelControls) this.refreshPanelControls();
    }

	// if we're supposed to autoDraw, and we don't have a parentElement already,
	//	draw us now.  This allows us to avoid sprinkling canvas.draw() commands
	//	in our XML or JS source.
    //
    // isc.noAutoDraw is a special internal flag to suppress autoDraw even for components that
    // explicitly request it - currently used in the ExampleViewer where we reparent components
    // as part of example loading.
	if (this.autoDraw && !this.parentElement && !isc.noAutoDraw) {
        
        if (isc.Browser.isSafari && !isc.Browser.isChrome &&
            isc.deferAutoDraw && !isc.Page.isLoaded() &&
            this.position != "relative")
        {
            isc.Page.setEvent("load", "if(window." + this.getID() + ")" + this.getID() + "._deferredAutoDraw()");
        } else {
            this.draw();
        }
    }
},

_deferredAutoDraw : function () {
    if (this.destroyed || this.isDrawn()) return;
    this.draw();
},

//>	@method	canvas.initWidget()
//
// For custom components, perform any initialization specific to your widget subclass.
// <P>
// When creating a subclass of any Canvas-based component, you should generally override
// this method rather than overriding +link{Class.init()}.  This is because Canvas has its own
// +link{Class.init()} override which performs some generally desirable initialization - see
// +link{Canvas.init()} for details.
// <p>
// This method is called by +link{Canvas.init()} when a component is create()d.  When
// overriding this method, You must call the superClass initWidget implementation, like
// so:
// <pre>
//    this.Super("initWidget", arguments);
// </pre>
// <P>
// In general, if you are going to call functionality supported by your superclass (eg calling
// addTab() when your superclass is a TabSet), call Super() first.  However, you can generally
// assign properties to <code>this</code> before calling Super() as a way of mimicking the
// effect of the property being passed to +link{Class.create(),create()} on normal instance
// construction.  For example, when subclassing a DynamicForm, you could set this.items to a
// generated set of items before calling Super().
// <P>
// NOTE: child creation: if you are creating a component that auto-creates certain children (eg
// a Window which creates a Header child), typical practice is to create those children
// immediately before drawing by overriding draw().  This postpones work until it is really
// necessary and avoids having to update children if settings are changed between creation and
// draw().  Alternatively, if you prefer callers to directly manipulate auto-created children,
// it's best to create them earlier in initWidget(), in order to allow manipulation before
// draw.
//
// @param	[arguments 0-N] (any)	All arguments initially passed to +link{Class.create()}
//
// @visibility external
//<
initWidget : function () { },
_$initWidget : "initWidget", // for calling Super

//>EditMode
setID : function (id) {
    // leave the old global name intact so that generated expressions that depend on a stable
    // id (such as timers or page events) continue to work.  Adding an entry to pointersToThis
    // means we'll clean up our old name on destroy.  However this won't handle renaming a
    // widget to the old name of another, so this is strictly editMode.
    var pointersToThis = this.pointersToThis = this.pointersToThis || [];
    pointersToThis.add({ object: window, property: this.ID });

    this.ns.EH.changeClickMaskID(this.getID(), id);

    this.ID = id;

    // new global reference
    window[id] = this;

    // regenerate DOM, which may have inline event handlers pointing to our old ID
    this.clear(); this.draw();
},
//<EditMode

// recursively clear out all global references to a Canvas and it's children and peers.  Used
// when we want to destroy and recreate a component, and want to delay the destroy (for
// performance reasons), but need to avoid colliding on IDs as the new component is created.
// NOTE: may be imperfect if the target has a non-peer, non-child component that it creates
// with a predictable global ID.
clearIDs : function () {
    this.clear();

    window[this.ID] = null;

    if (this.children) {
        for (var i = 0; i < this.children.length; i++) {
            this.children[i].clearIDs();
        }
    }
    if (this.peers) {
        for (var i = 0; i < this.peers.length; i++) {
            this.peers[i].clearIDs();
        }
    }
},

//> @method canvas.getByLocalId()
// Retrieve a child of this Canvas by it's local ID.
// <p>
// A "local ID" is name for a child widget which is unique only for this parent, and not globally
// unique as is required for +link{Canvas.ID}.  Widgets receive local IDs when loaded via
// +link{RPCManager.loadScreen()} or +link{RPCManager.cacheScreens()} and
// +link{RPCManager.createScreen()}.
//
// @param localId (String) local ID of the Canvas
// @return (Canvas) the Canvas, or null if not found
// @visibility external
//<
getByLocalId : function( localId ) {

    
    if (this._localIds) {
        return this._localIds[localId];
    } else if (this._screen && this._screen._localIds) {
        return this._screen._localIds[localId];
    } else {
        return null;
    }
},

// Drawn state
// --------------------------------------------------------------------------------------------

//>	@method	canvas.getDrawnState()
//      What state of drawing is this canvas in? Options:
//      - undrawn       :   has never been drawn or has been cleared
//      - drawingHandle :   in the process of drawing - handle not yet fully written out to the DOM
//      - handleDrawn   :   handle completely written out to the DOM, but children, peers etc. not drawn
//      - complete      :   fully drawn, including children, peers, etc.
//		@group	drawing
//
//<
getDrawnState : function () {
    // Note: In theory only one of the 3 possible drawing flags should be true at a time
    // (guaranteed if cavas.setDrawnState() is used to set the drawn state).
    // However, check them in reverse order from which they would be applied, in case
    // this._drawn gets set on drawing completion, but this._handleDrawn is not cleared at the same
    // time.
    if (this._drawn == true) return isc.Canvas.COMPLETE;
    if (this._handleDrawn == true) return isc.Canvas.HANDLE_DRAWN;
    if (this._drawingHandle == true) return isc.Canvas.DRAWING_HANDLE;
    return isc.Canvas.UNDRAWN;

},

//>	@method	canvas.setDrawnState()
//      Set state of drawing for this canvas.
//      This should be called rather than directly setting this._drawn, etc. flags.
//      Options:
//      - undrawn       :   has never been drawn or has been cleared
//      - drawingHandle :   in the process of drawing - handle not yet fully written out to the DOM
//      - handleDrawn   :   handle completely written out to the DOM, but children, peers etc. not drawn
//      - complete      :   fully drawn, including children, peers, etc.
//		@group	drawing
//
//<
setDrawnState : function (state) {
    if (state == isc.Canvas.COMPLETE) this._drawn = true;
    else this._drawn = false;

    if (state == isc.Canvas.HANDLE_DRAWN) this._handleDrawn = true;
    else this._handleDrawn = false;

    if (state == isc.Canvas.DRAWING_HANDLE) this._drawingHandle = true;
    else this._drawingHandle = false;

    // If state is isc.Canvas.UNDRAWN, no need to do anything as we will have already cleared
    // all the drawn flags
},


//>	@method	canvas.isDrawn()    ([])
//      Returns the boolean true, if the widget has been completely drawn, and false otherwise.
//  @visibility external
//  @group	drawing
//  @return	(Boolean)	true if drawn, false if not drawn
//<
isDrawn : function () {
	return !!this._drawn;
},

handleDrawn : function () {
    return !!this._handleDrawn;
},

// ----------------------------------------------------------------------------------------

//>	@method	canvas.getID()  ([])
//      When a widget instance is created, it is assigned a unique global identifier that can be
//      used to access the instance by name. The getID method returns this ID for a particular
//      instance. Global IDs are essential when you need to embed a widget reference in a string,
//      usually a string that will be evaluated in the future and/or in another object, where you
//      may not have access to a variable or parameter holding the widget's reference.
//
//  @visibility external
//  @return	(string)	global identifier for this canvas
//<
getID : function () {
    if (this.ID == null) this.ns.ClassFactory.addGlobalID(this);
	return this.ID;
},

// so that we look more like DOM objects
getAttribute : function (attributeName) { return this[attributeName] },

// Drawing
// --------------------------------------------------------------------------------------------

//> @method canvas.getInnerHTML() (A)
// Return the inner HTML for this canvas. Called when the canvas is drawn or redrawn;
// override to customize.
//
// @return (HTMLString) HTML contents of this canvas
// @group drawing
// @visibility external
//<

getInnerHTML : function (printCallback) {
    var html;
    if (!this.containsIFrame()) html = this.getContents();
    else {
        var url = this.getContentsURL();
        // support special prefixes, eg [APPFILES]
        url = isc.Page.getURL(url);
        // support params, actually doc'd under HTMLFlow only
        if (isc.rpc) url = isc.rpc.addParamsToURL(url, this.contentsURLParams);

        
        isc.EventHandler.registerMaskableItem(this, true);
        html = this.getIFrameHTML(url);
    }
    return html;
},

getIFrameHTML : function (url) {
    // XXX: may need to be updated when/if we add code to auto-size to contents of iframe.
    
    return "<iframe height='100%' width='100%' scrolling='" +
        (this.overflow == isc.Canvas.HIDDEN ? "no'" : "auto'") +
        (isc.Browser.isSafari ? " id=" + this._getDOMID("iframe") : "") +
        " frameborder='0'" +
        " src=\"" + url +"\"></iframe>";
},

// In some browsers IFRAMEs with 100% are not sized correctly / don't react to resizes,
// so we we manually resize after draw and resize.

iFrameHeightAdjustment:0,
_sizeIFrame : function () {
    var drawnState = this.getDrawnState();
    if (drawnState != isc.Canvas.COMPLETE && drawnState != isc.Canvas.HANDLE_DRAWN) return;

    var handle = this.getHandle(),
        frameElement = handle ? handle.firstChild : null;
    // handle should be drawn so these sanity checks to get at the frame element may be
    // unnecessary.
    if (frameElement == null) return; // should never happen

    
    frameElement.style.height =
        (this.getInnerContentHeight() - this.iFrameHeightAdjustment) + isc.px;
},

// internal signature, allows timing all getInnerHTML overrides from the Canvas level
_getInnerHTML : function (printCallback) {
    if (isc._traceMarkers) arguments.__this = this;
    
    var self = this;
    var completeInnerHTMLFun = function completeInnerHTMLFun(HTML) {
        
        if (self._appendHTML) {
            var appendHTML = self._appendHTML.join(isc.emptyString);
            HTML = (HTML == null || HTML == isc.nbsp ? appendHTML : HTML + appendHTML);
        }
        if (printCallback != null) self.fireCallback(printCallback, "HTML", [HTML]);
        return HTML;
    };
    var HTML = this.getInnerHTML(completeInnerHTMLFun);
    if (HTML === false) {
        return false;
    } else {
        printCallback = null;
        return completeInnerHTMLFun(HTML);
    }
},

//> @method canvas.setSnapOffsetLeft()
// Setter for +link{Canvas.snapOffsetLeft}.
// @param snapOffsetLeft (Integer) new snapOffsetLeft value.
//<
setSnapOffsetLeft : function (snapOffsetLeft) {
    var oldSnapOffsetLeft = this.snapOffsetLeft;
    this.snapOffsetLeft = snapOffsetLeft;
    if (oldSnapOffsetLeft != snapOffsetLeft) this._resolvePercentageSize();
},

//>	@method	canvas.readyToDraw()
// Determines whether there's any reason why draw() should not proceed and draw the canvas at this
// time.  Logs errors and warnings if appropriate, so if you override draw() just add a check
// like:<br>
// &nbsp;&nbsp;if (!this.readyToDraw()) return this;
//		@group	drawing
//
//		@return	(boolean)	True if draw() should proceed.
//<
readyToDraw : function () {

    // If we're already drawn, or in the process of drawing, log a warning and return false
    var drawingState = this.getDrawnState();
    if (this.getDrawnState() != isc.Canvas.UNDRAWN) {
        var drawingState = this.getDrawnState();

        this.logWarn("draw() called on widget with current drawn state: " + drawingState +
                     (drawingState == isc.Canvas.COMPLETE ?
                      ", use redraw() instead." : ", ignoring.") + this.getStackTrace(),
                     "drawing");
        return false;
    }

    // If showIf returns false, we're not ready to draw.
    // if showIf has not been overridden don't bother to evaluate it
    if (this.showIf != null) {
        // CALLBACK API:  available variables:  "canvas"
        // Convert a string callback to a function
        this.convertToMethod("showIf");

        // don't draw if the showIf returns false
        // (Will still draw if this function returns no explicit value - EG observation function /
        //  no-op function)
        if (this.showIf(this) == false) return false;
    }

    // refuse to draw if we have zero or negative area
    if (this.getHeight() <= 0 || this.getWidth() <= 0) {
        
        if (this._pendingPageResizeForZeroSize) {
            this._deferDrawForPageSize();
            return false;
        }

        // NOTE: drawing with negative area appears to work in all browsers, then in Nav4 you get
        // non-local partial failures
		//>DEBUG
		this.logWarn("negative or zero area: height: " + this.getHeight() + ", width: " + this.getWidth() +
                     ", refusing to draw" + this.getStackTrace(), "drawing");
		//<DEBUG
        return false;
    }

    // If we have a pending delayed draw event, don't draw.
    // (NOTE: we could have this clear that event, and attempt to proceed instead, but
    //  the desired usage of the deferred draw feature is simply to delay drawing while it is
    //  not legal for some reason...
    //  If we instead wanted to have a draw occur on some other event like a click, we'd probably
    //  use a call to show() at the appropriate time instead).
    if (this.deferredDrawEvent != null) {
		//>DEBUG
		this.logInfo("draw() called while object already pending a delayed draw - no action to take",
                        "drawing");
		//<DEBUG
		return false;
    }

    // If we have a parent element:
    //
    // If the parentElement has written out it's start tag it's ok to proceed, as we'll
    // document.write() the child in the correct scope
    // If the parentElement has completely written out it's HTML, it's ok to proceed, as we'll
    // use the _insertHTML method to add the child to the parent using the DOM.
    //
    // If the parent has never drawn though we don't want to proceed, as we will either
    // document.write() into the wrong scope, or attempt to _insertHTML() into a non-existent handle
    if (this.parentElement != null &&
            (!isc.isA.Canvas(this.parentElement) ||
             this.parentElement.getDrawnState() == isc.Canvas.UNDRAWN) )
    {
        this.logWarn("Attempt to draw child of an undrawn parent - ignoring" +
                     this.getStackTrace(), "drawing");
        return false;
    }

    //>Safari
    if (isc.Browser.isSafari && !isc.Page.isLoaded()) {
        var safariVersion = isc.Browser.safariVersion;
        
        if (parseInt(safariVersion) < 100) {
            this.drawDeferred();
            return false;
        
        } else {
            // this hasn't been the case for quite some time - no longer makes sense to carp
            // about this
            /*
            if (!isc.Canvas._safariDeferDrawWarned) {
                isc.Canvas.logWarn(
                    "Isomorphic recommends drawing components after page load in Safari, as " +
                    "some sizing information may not be available until the page has " +
                    "completely loaded.  If you are encountering sizing issues for any " +
                    "component try setting 'autoDraw' to false, and setting up an event " +
                    "to draw the component on the Page level 'load' event.",
                    "drawing"
                );
                isc.Canvas._safariDeferDrawWarned = true;
            }
            */
        }
    }
    //<Safari

    // Otherwise it's ok to draw.
    return true;

},


// Both IE and Chrome can hit a case where on initial page render the
// page size isn't yet reported.
// In this case we delay draw of top level %-sized canvases using this method

_deferDrawForPageSize : function () {
    // If the page is already marked as loaded, draw on next idle loop
    if (isc.Page.isLoaded()) this.drawDeferred();
    else {
        // draw on, or after page load
        isc.Page.setEvent("load", this.getID() + "._fireDeferredDrawForPageResize()");

    }
},
_fireDeferredDrawForPageResize : function () {
    if (this.destroyed) return;

    // In Chrome we've seen the page size still be zero during the page.onload event handlers
    // In this case delay draw further
    
    if (isc.Page.getWidth() == 0 || isc.Page.getHeight() == 0) {
        this.delayCall("draw", null, 100);
    }
    else {
        this.draw();
    }

},

// determine whether the Canvas must draw via doc.write(), which is needed for Canvii created
// by isc.Canvas.start(), in order to allow any <SCRIPT> blocks embedded in their
// Canvas.contents to execute in IE.
// Automatically checks for situations where its impossible to use doc.write() (parent handle
// already drawn).
_mustDocumentWrite : function () {
    
    return false;
},
// determine whether a Canvas would like to draw via doc.write()
_requestsDocumentWrite : function () {
    // flag set by isc.Canvas.start()
    if (this._forceDocumentWrite) return true;

    // if a parent is in the middle of drawing via doc.write()ing, we have to draw via
    // doc.write() or we won't insert into the parent.  A parent will be forced to doc.write()
    // if any of its children must doc.write() - and once it's doc.write()ing, all its children
    // must do so - otherwise they'll crash while looking for the parent handle (which is only
    // partially written in this mode)
    var theParent = this.parentElement;
    while (theParent) {
        if (theParent._forceDocumentWrite) return true;
        theParent = theParent.parentElement;
    }

    // if we have any children, recursively, that need to document.write(), then we have to use
    // document.write()
    if (this.children) {
        //this.logWarn("checking children: " + this.children.getProperty("_forceDocumentWrite"));
        for (var i = 0; i < this.children.length; i++) {
            if (this.children[i]._mustDocumentWrite()) return true;
        }
    }
    return false;
},

//>	@method	canvas.draw()   ([])
//      Draws the widget on the page.
//  @visibility external
//  @group  drawing
//  @return (canvas)    Pointer to this canvas.  Returned so statements like the following will
//						work:<br>
//                          var myCanvas = Canvas.newInstance({...}).draw();
//<
//  "showing" parameter: if true, draw() was called from show().  By default draw() will call show()
//  on completion if the widget is visible as some widgets use show() as an entry point to
//  initialize certain widget properties.  If draw() was called from show(), always avoid calling
//  show() a second time.
draw : function (showing) {

    if (isc._traceMarkers) arguments.__this = this;
    // verifies that it is legal to draw
    if (!this.readyToDraw()) return this;

    

    // auto-assigned tab-index:
    // If this widget is focusable, getTagStart will call 'getTabIndex()' to determine the
    // tabIndex to write into the handle / focusProxy.
    // If this widget has no specified tabIndex, this method will auto-assign the next available
    // tab index to this widget.
    // The behavior this gives us is that focusable canvii with no specified tab index are
    // inserted into the tab order for the page in the order in which they are drawn, which
    // is appropriate.
    // We make any widgets showing scrollbars focusable by default.  If a widget has
    // overflow:auto, however we don't know whether it will be scrollable until it has been
    // drawn and adjustOverflow gets fired.  This can happen on a delay / after page load in
    // some browsers, so that widget will end up at the end of the page's tab order rather than
    // at the expected position.
    // Avoid this issue by calling 'getTabIndex()' on draw() for any overflow:auto widgets to
    // ensure auto-allocated tab-indices are allocated in the order in which these are drawn
    // onto the page.
    
    if (this.overflow == isc.Canvas.AUTO) this.getTabIndex();

	//>DEBUG
    if (this.logIsInfoEnabled(this._$draws)) {
    	this.logInfo("draw(): drawing " + this.Class +
                     (this.parentElement ? " with parent: " + this.parentElement : "") +
                     (!isc.Page.isLoaded() ? " before page load" : "") +
                     (this.logIsDebugEnabled(this._$draws) ? this.getStackTrace() : ""),
                     this._$draws);
    }
    // track total draws
    this._addStat(this._$draws);

    //<DEBUG

    // If we are databound, and autoFetchData is true, do a one time fetch on initial draw.
    var fetchQueued = this.doInitialFetch();

    // If we have any peers, call the 'predrawPeers()' method.
    // This method will draw any peers marked with the "_drawBeforeMaster" flag (set up by passing
    // a parameter to addPeer()), before continuing with the drawing process.
    // We do this because it allows developers to specify some peers to be drawn before the master
    // gets drawn, when the master is controlling the drawing of the peers.
    // This is valuable in some cases as the master is guaranteed that such preDraw peers will be
    // drawn while its own draw() is firing - for example this is used in stretchImgButtons, where
    // the getInnerHTML() method makes use of the drawn size of the label (peer) for the button
    // to determine the desired size of the images.
    // See the methods:
    //  predrawPeers(), drawPeers(), redrawPredrawnPeers(), redrawPeers() and addPeer()
    if (this.peers != null && this.peers.getLength() > 0) {
        

        this.predrawPeers();
        
    }

    
    var fixOpacity = (isc.Browser.isIE && this.fixIEOpacity && !this.masterElement),
        cacheOffsetCoords = isc.Element.cacheOffsetCoords;

    if (this.position == isc.Canvas.RELATIVE) {
        this.cacheOffsetCoords = false;
        cacheOffsetCoords = false;
    }

    if (fixOpacity || cacheOffsetCoords) {

        var parent = this.parentElement;
        while (parent) {
            if (fixOpacity) {
                if (parent.opacity != null && parent.opacity != 100) {
                    //this.logWarn("opacity set on parent: " + parent);
                    this.setOpacity(100, null, true);
                    fixOpacity = false;
                    if (!cacheOffsetCoords) break;
                }
            }
            if (cacheOffsetCoords) {
                if (parent.position == isc.Canvas.RELATIVE) {
                    this.cacheOffsetCoords = false;
                    cacheOffsetCoords = false;
                    if (!fixOpacity) break;
                }
            }
            parent = parent.parentElement;
        }
    }
    
    if (cacheOffsetCoords) this.cacheOffsetCoords = true;

    // if this.htmlElement and this.matchElement are set, resize the canvas to fit the
    // target element before drawing
    if (this.htmlElement != null && this.matchElement) {
        if (isc.isA.String(this.htmlElement)) this.htmlElement = isc.Element.get(this.htmlElement);

        // We want the available inner width of the element.

        // For things that don't overflow this will be the specified width or, if there isn't
        // one, the clientWidth - padding
        
        var width = isc.Element.getNativeInnerWidth(this.htmlElement),
            height = isc.Element.getNativeInnerHeight(this.htmlElement);
        this.setWidth(width);
        this.setHeight(height);
    }

    

    var parentElement = this.parentElement;
    var mustDocumentWrite =
        // the page is not loaded
        (!isc.Page.isLoaded() &&
         // if this widget is going to draw at a specified, pre-existing position in the DOM,
         // it needs to do DOM insertion, not document.write()
         !this.drawContext &&
         // this Canvas is relative and has no parent, hence needs to doc.write() HTML at
         // the current document position.  Note that children of a relatively positioned
         // parent insert into the parent via DOM insertion, not doc.write()
         (parentElement == null && this.position == isc.Canvas.RELATIVE)
        );

    
    mustDocumentWrite = mustDocumentWrite || this._mustDocumentWrite();

    // Determine whether we should insert innerHTML as a separate commit.  This allows children
    // to be drawn before, during or after innerHTML generation.
    var separateContentInsertion = this.separateContentInsertion;
    
    if (isc.Page.isLoaded() || !mustDocumentWrite) {
        

        
        this._insertHTML(!separateContentInsertion);

        

        if (separateContentInsertion) this._updateParentHTML();

        this.drawChildren();

        
        this._completeHTMLInit();

        
	} else {
        

        var parent = this.parentElement;

        // detect the case of getting fooled about whether the page is loaded and fix it in
        // IE, warning for other browsers.
        if ((isc.Browser.isOpera || isc.Browser.isIE) && this.getDocument().readyState == "complete")
        {
            isc.Page.finishedLoading();
            
        }
        

        this._writeHTML();

        
	}

    // If we queued the fetch, lets send it off now.  Note this is *after* children have drawn,
    // hence naturally combines the initial fetches of any hierarchy of databound widgets.
    if (fetchQueued) isc.RPCManager.sendQueue();

    //>FocusProxy If we're using a focusProxy, create it now
    if (this._useFocusProxy && this._canFocus()) this.makeFocusProxy();
    //<FocusProxy

    
    if (this.accessKey != null && this._useAccessKeyProxy() && this._canFocus()) {
        this._makeAccessKeyProxy();
    }

    // If we're enforcing scroll size, ensure the scroll-size-enforcer div is present
    if (this._enforcingScrollSize != null)
        this.enforceScrollSize(this._enforcingScrollSize[0], this._enforcingScrollSize[1]);

    // If we're masked by a hard clickmask, ensure EH takes the necessary
    // action to physically mask us
    
    if (this._isHardMasked()) isc.EH._hardMaskTargets([this]);

    //>CornerClips
    if (this.clipCorners) this._finishCornerClips(); //<CornerClips

    


    // set up the _currentlyVisible flag so we fire visibilityChanged at the right times.
    // (That notification is suppressed while we're undrawn)
    this._currentlyVisible = this.isVisible();
    // At this point we've written out the HTML into the DOM.
    // If the widget is visible, call show() on it; certain widgets override show() to do
    // perform miscellaneous tasks associated with displaying the widget - we would want to
    // perform these when the widget is drawn, too

    if (!showing && this._currentlyVisible) this.show();
    // for uses like Canvas.draw().moveTo(...)

    

    // notify parent / masterElement that we've been drawn
    if (this.parentElement) this.parentElement.childDrawn(this);
    if (this.masterElement) this.masterElement.peerDrawn(this);

    // If we're a top level canvas and our bottom / right exceed the edges of the browser
    // viewport, we effectively change the size of the window.
    // We don't get a native browser resize notification on this, so
    // explicitly run the _pageResize() method now.
    // We also do this on moved / resized for top level canvii.
	if (this.parentElement == null && isc.Page.isLoaded() && !isc.Page.pollPageSize)
	{
		if (this.getPageRight() >= isc.Page.getWidth() ||
			this.getPageBottom() >= isc.Page.getHeight())
		{
			isc.EH.fireOnPause("checkForBodyOverflowChange",
								{target:isc.Canvas, methodName:"checkForPageResize"},
								100);
		}
    }


    // If we're relatively positioned, and we're a top level canvas, fire moved() on page
    // resize to allow our peers to reposition themselves
    // Note: this initial setup needs to fire on draw even if the widget is not visible
    // as our peers still need to be informed of when the masterElement moves.
    if (this.parentElement == null && this.position == this._$relative) {
        // remember our current page coordinates
        this._preResizePageLeft = this.getPageLeft();
        this._preResizePageTop = this.getPageTop();
        isc.Page.setEvent(
            "resize",
            this,
            isc.Page.FIRE_ONCE,
            "_relativePageResized"
        );
    }


    

    

    
    this.onDraw();

    // If we're overflow visible and we've overflowed, fire the _resized notification
    
    if (!this._deferredOverflow && this.overflow == isc.Canvas.VISIBLE) {
        var visWidth = this.getVisibleWidth(),
            visHeight = this.getVisibleHeight(),
            w = this.getWidth(), h = this.getHeight();

        if (visWidth > w || visHeight > h) {
            this._resized(visWidth - w, visHeight - h, "Overflow on initial draw");
        }
    }
    return this;
},


onDraw:function () {
},

// empty implementation overridden by DBC
doInitialFetch : function () {},

// output this widget's HTML via document.write()
_writeHTML : function () {

    // mark that we've started drawing the handle - this allows us to detect recursive calls to
    // draw() and other invalid cases.
    this.setDrawnState(isc.Canvas.DRAWING_HANDLE);

    var doc = this.getDocument(),
        separateContentInsertion = this.separateContentInsertion;

    if (this.children != null && this._mustDocumentWrite()) {
        // now that the parent is using doc.write(), all children must do so as well,
        // regardless of whether the children are marked _forceDocumentWrite - otherwise
        // they'll try to look up our handle for insertAdjacentHTML() and fail because it'll
        // only be partially written.
        this._forceDocumentWrite = true;

        //this.logWarn("using legacy doc.write() path");
        
        var tagStart = this.getTagStart(),
            tagEnd = this.getTagEnd();
        doc.write(separateContentInsertion ? tagStart : tagStart + this._getInnerHTML())
        this.drawChildren();
        doc.write(separateContentInsertion ? this._getInnerHTML() + tagEnd : tagEnd);

        // Mark that we've finished drawing the handle into the DOM
        this.setDrawnState(isc.Canvas.HANDLE_DRAWN);

    } else {

    // write the complete parent handle into the DOM, then have children, if any, insert into
    // completed handle
    doc.write(
        isc.SB.concat(this.getTagStart(),
                      (separateContentInsertion ? null : this._getInnerHTML()),
                      this.getTagEnd()
        )
    );

    // Mark that we've finished drawing the handle into the DOM
    this.setDrawnState(isc.Canvas.HANDLE_DRAWN);

    // if we are separately inserting content, insert the parent's content now.  Note that it
    // is legal for some children to get manually drawn at this point, which allows parents to
    // write out content that is dependent on child sizes.
    if (separateContentInsertion) this._updateParentHTML();

    // draw children if we have any
    this.drawChildren();

    }

    // call completeHTMLInit to draw peers, set up events, adjustOverflow, and mark us as
    // completely drawn
    this._completeHTMLInit();

    
    if (isc.Browser.isMoz && this.getScrollingMechanism() == isc.Canvas.NATIVE)
        this.checkNativeScroll();

	// return a pointer to the object
	return this;
},

// Draw after a pause (by default on page load or the next idle)
// Holds onto the deferred draw event as this.deferredDrawEvent, so you can keep track of
// cases where we're waiting to draw, and do the appropriate thing with calls to clear, etc.

drawDeferred : function () {

    var eventType = (isc.Page.isLoaded() ? "idle" : "load");

    if (this.deferredDrawEvent != null) {
		this.logInfo("drawDeferred() called when object is already pending drawing " +
                        "- No action to take.");

        return;
    }

    var ID = this.getID();

    this.deferredDrawEvent =
            isc.Page.setEvent(eventType,
                              "delete " + ID + ".deferredDrawEvent;" + ID + ".draw();",
                              isc.Page.FIRE_ONCE);
},

// Printing
// --------------------------------------------------------------------------------------------



//> @groupDef printing
// The browser's built-in support for printing will at best print what you see, which in the
// case of a web application will often be useless, illegible, or partial.
// <P>
// SmartClient has specialized printing support that can take any page built with SmartClient
// components and provide a reasonable printed view.  The default printed view:
// <P>
// <ul>
// <li> renders components without clipping or scrolling regions, so that a scrolling grid
// shows all loaded rows
// <li> removes certain decorative images, such as image-based backgrounds, which may print
// poorly in black and white
// <li> converts editing controls into static representations of the data being edited
// <li> removes interactive elements such as buttons and menus, which don't work on paper and
// waste space
// </ul>
// The default printed view can be customized with settings and method overrides as necessary,
// including the ability to created printed representations of custom components you have
// created.
//
// @title Printing
// @visibility external
//<

//> @attr canvas.printChildrenAbsolutelyPositioned (Boolean : false : IRWA)
// Should this canvas print its children absolutely positioned when generating
// +link{classMethod:canvas.getPrintHTML(),printable HTML}.
// <P>
// By default explicitly specified absolute positioning and sizing is ignored when generating
// print HTML. This is done intentionally: there is no way for the framework to predict how
// explicit sizes will translate to a the printed page and if HTML for printing includes
// the same absolute positioning and sizing as is displayed within an application it is very
// common to encounter undesirable effects, such as seeing tables get broken over several
// pages horizontally when there is enough room to print them on a single page of paper.
// <P>
// In some cases, however, a developer may wish to have explicit sizing and positioning
// respected within the print-view.
// Setting this attribute to <code>true</code> will cause this to occur.
//
// @group printing
// @visibility external
//<

//>	@method	canvas.getPrintHTML() [A]
// Retrieves printable HTML for this component and all printable subcomponents.
// <P>
// By default any Canvas with children will simply collect the printable HTML of its children
// by calling getPrintHTML() on each child that is considered +link{canvas.shouldPrint,printable}.
// If a callback is provided, then null is always returned and the callback is fired asynchronously.
// <P>
// If overriding this method for a custom component, you should <b>either</b> return a String
// of printable HTML directly <b>or</b> return null and fire the provided callback using
// +link{Class.fireCallback}.
// <P>
// To return an empty print representation, return an empty string ("") rather than null.
// <P>
// The <code>printProperties</code> argument, if passed, must be passed to any subcomponents on
// which <code>getPrintHTML()</code> is called.
// <P>
// <B>NOTE:</B> Expecting a direct return value from the default implementation is deprecated usage.
// This is because small changes to an application (such as adding a few more data points to a chart
// or adding another button) or using certain browsers can make it necessary to generate the HTML
// asynchronously. Thus, application code should not rely on the return value and always pass
// a callback.
//
// @param [printProperties] (PrintProperties) properties to configure printing behavior - may be null.
// @param [callback] (Callback) optional callback. This is required to handle cases where HTML
//                  generation is asynchronous - if a method generates HTML asynchronously, it should return
//                  null, and fire the specified callback on completion of HTML generation. The
//                  first parameter <code>HTML</code> should contain the generated print HTML.
//                  The callback is only called if null is returned. Furthermore, the default
//                  getPrintHTML() implementation always returns null and fires the callback
//                  when a callback is provided.
// @return (HTMLString) null if the print HTML is being generated asynchronously and/or a callback
// is provided; otherwise, the direct print HTML for this component (but note that returning
// direct print HTML is a deprecated feature).
// @group printing
// @visibility external
//<
_$html:"html",
getPrintHTML : function (printProperties, callback) {
//!DONTOBFUSCATE  (obfuscation breaks the inline function definitions)

    this.isPrinting = true;
    // Always copy this.printProperties onto the printProperties block passed in
    // [Allows you to always suppress controls for certain components only, etc.]
    printProperties = isc.addProperties({}, printProperties,this.printProperties);
    // store the top level canvas so we know not to writing out positioning info
    // for it.
    if (printProperties.topLevelCanvas == null) {
        printProperties.topLevelCanvas = this;
        printProperties.isDrawn = this.isDrawn();
        printProperties.isVisible = this.isVisible();
    }

    // omitControls / includeControls
    // omitControls is an array of widget classes which should be ommitted as they are
    // controls.
    // By default all subclasses of these controls will also be ommitted - however we can
    // override that behavior by including a subclass in the 'includeControls' array
    if (printProperties.omitControls == null)
        printProperties.omitControls = isc.Canvas.printOmitControls;
    if (printProperties.includeControls == null)
        printProperties.includeControls = isc.Canvas.printIncludeControls;

    // pick up the abs-pos flag (set by our parent if we are to be absolutely positioned)
    var absPos = printProperties.absPos;
    this.currentPrintProperties = printProperties;
    var HTML = [this.getPrintTagStart(absPos), , , this.getPrintTagEnd(absPos)];

    var wentAsync = false;

    var self = this;
    var continuePrintHTMLFun = function continuePrintHTMLFun(printInnerHTML) {
        HTML[1] = printInnerHTML;

        // Not all print properties should be passed onto our children.
        // clear the "inline" setting before passing the printProperties block on.
        delete printProperties.inline;

        // set the abs-pos flag so our children render out absolutely positioned within us.
        
        printProperties.absPos = self.printChildrenAbsolutelyPositioned;

        // clear up any gaps etc in the user-defined omitComponents block
        if (printProperties.omitComponents) {
            var omitComponents = printProperties.omitComponents
            for (var i = 0; i < omitComponents.length; i++) {
                if (isc.isA.String(omitComponents[i]))
                    omitComponents[i] = window[omitComponents[i]];
                if (!isc.isAn.Instance(omitComponents[i])) omitComponents[i] = [];
            }
            omitComponents.removeEmpty();
        }

        var children = self.getPrintChildren();

        var completePrintHTML = function completePrintHTML(childrenHTML) {
            // pass the closure vars through to the completePrintHTMLCallback method
            return self.completePrintHTMLCallback(childrenHTML, HTML, wentAsync, callback);
        };

        if (children != null && children.length > 0) {
            var childrenHTML = [],
                childCount = children.length,
                completedCount = 0;

            var childHTMLComplete = function childHTMLComplete(childIndex, html) {
                childrenHTML[childIndex] = html;
                ++completedCount;
                //self.logDebug("Child: " + childIndex + " - " + completedCount + "/" + childCount);
                if (completedCount == childCount) {
                    return completePrintHTML(childrenHTML);
                }
            };

            var thisHTML = null;
            for (var i = 0; i < childCount; i++) {
                var child = children[i];

                //this.logDebug("HTML for child: " + i);

                // assembly body string to hard-code index of HTML insertion point to ensure
                // correct order
                var func = (function (i) {
                    return function (html) {
                        return childHTMLComplete(i, html);
                    }
                })(i);

                // ask child to generate HTML and pass optional async callback
                var childHTML = self.getChildPrintHTML(child, printProperties, func);

                // child didn't go async, so it won't be calling its async callback and we need
                // to do it.
                if (childHTML != null) {
                   //this.logDebug("child: " + i + " returned HTML synchronously");
                   thisHTML = childHTMLComplete(i, childHTML);

                // child went async
                } else {
                    //this.logDebug("child: " + i + " went async -> " + child.getID());
                    wentAsync = true;
                }
            }

            if (wentAsync || callback != null) {
                return null;
            } else {
                return thisHTML;
            }
        } else {
            // no children, finish up
            return completePrintHTML();
        }
    };

    // Omit content if we have children (matches standard rendering behavior)
    // - Check this.children - this may include children (such as controls) which are omitted from
    //   the print children [but still mean the content needs to be rendered]
    if (!this.children || this.children.length == 0 || this.allowContentAndChildren) {
        var printInnerHTML = this.getPrintInnerHTML(continuePrintHTMLFun);
        if (printInnerHTML == null) {
            wentAsync = true;
            return null;
        } else {
            return continuePrintHTMLFun(printInnerHTML);
        }
    } else {
        return continuePrintHTMLFun();
    }
},

getChildPrintHTML : function (child, printProperties, callback) {
    return child.getPrintHTML(printProperties, callback);
},

// This is a callback fired once we've got printHTML for all our children stuffed into
// an array.
// Either returns the HTML or fires the original print callback
completePrintHTMLCallback : function (childrenHTML, HTML, wentAsync, callback) {     
    this.isPrinting = false;
    
    HTML[2] = this._joinChildrenPrintHTML(childrenHTML);
    
    HTML = HTML.join(isc.emptyString);
    delete this.currentPrintProperties.absPos;
    delete this.currentPrintProperties;

    // If printHTML generation went asynchronous or a callback was provided, then fire
    // the provided callback in a timeout. This resets the stack and makes the closures
    // and other temporary objects eligible for garbage collection.
    if (wentAsync || callback != null) {
        this.delayCall("fireCallback", [callback, "HTML,callback", [HTML, callback]]);
        return null;

    } else {
        if (!isc.Canvas._loggedGetPrintHTMLDeprecatedUsageWarning) {
            isc.logWarn("Expecting a direct return value from getPrintHTML() is deprecated. " +
                         "The recommended usage is to pass a callback always. See the documentation " +
                         "for more information on the reason for always passing a callback.");
            isc.Canvas._loggedGetPrintHTMLDeprecatedUsageWarning = true;
        }
        return HTML;
    }
},

_joinChildrenPrintHTML : function (childrenHTML) {
    if (isc.isAn.Array(childrenHTML)) childrenHTML = childrenHTML.join(isc.emptyString);
    return childrenHTML;
},


_$nbsp:isc.nbsp,
getPrintInnerHTML : function (callback) {
    
    var self = this;
    var completePrintInnerHTMLFun = function completePrintInnerHTMLFun(HTML) {
        var hasChildren = self.children != null && self.children.length > 0;
        // allowContentAndChildren is now true by default but typically widgets will have no
        // content (actually &nbsp;). Don't write out this character if the widget also has children.
        // Since print views render children stacked up, relying on normal relative positioning, it'll
        // introduce odd blank lines.
        if (hasChildren && HTML == self._$nbsp) HTML = isc.emptyString;
        if (callback != null) self.fireCallback(callback, "HTML", [HTML]);
        return HTML;
    };
    var HTML = this._getInnerHTML(completePrintInnerHTMLFun);
    if (HTML === false) {
        return null;
    } else {
        callback = null;
        if (HTML == null) HTML = isc.emptyString;
        return completePrintInnerHTMLFun(HTML);
    }
},

// getPrintChildren() -- returns the set of children we will include in our printHTML
// Split into a separate method for ease of overriding
getPrintChildren : function () {
    var children = this.children;
    if (!children || children.length == 0) return;
    var printChildren = [];
    for (var i = 0 ; i < children.length; i++) {
        if (this.shouldPrintChild(children[i])) printChildren.add(children[i]);
    }
    return (printChildren.length > 0) ? printChildren : null;
},

//> @attr canvas.shouldPrint (boolean : null : IRW)
// Whether this canvas should be included in a printable view.
// <P>
// Default is to:
// <ul>
// <li> omit all peers (edges generated by showEdges:true, etc)
// <li> omit anything considered a "control", such as a button or menu (see
// +link{PrintProperties.omitControls})
// <li> include everything else not marked shouldPrint:false
// </ul>
//
// @group printing
// @visibility external
//<

// shouldPrintChild - called by getPrintChildren() to determine which children need printing
shouldPrintChild : function (child) {

    if (child.shouldPrint != null) return child.shouldPrint;

    // omit peers for now to suppress edges, backmask, etc.
    if (child.masterElement) return false;

    var printProperties = this.currentPrintProperties,
        omitControls = printProperties.omitControls,
        omitComponents = printProperties.omitComponents;

    if (!isc.isAn.Instance(child) ||
        (omitComponents && omitComponents.contains(child)))
    {
        return false;
    }
    // omitControls is an array of widget classNames to skip
    if (omitControls) {
        // exception, if control is present in "includeControls" array, don't skip it. This is
        // useful for cases where we have a specific subclass of an ommitted controls class which
        // we want to include
        
        var includeControls = printProperties.includeControls;
        if (includeControls && includeControls.length > 0) {
            for (var i = 0; i < includeControls.length; i++) {
                var cName = includeControls[i];
                if (isc.isA[cName] && isc.isA[cName](child)) return true;
            }
        }
        for (var i = 0; i < omitControls.length; i++) {
            var cName = omitControls[i];
            if (isc.isA[cName] && isc.isA[cName](child)) {
                return false;
            }
        }
    }

    // If a developer calls getPrintHTML() on something undrawn or hidden directly we should
    // respect it. However if the method is called on a parent with undrawn/hidden children
    // we should skip the children by default.
    if ((!child.isDrawn() && printProperties.isDrawn) ||
         (!child.isVisible() && printProperties.isVisible)) return false;

    return true;
},

// _fixPNG() -- apply the .png workaround in IE.
// Will only be called if _fixPNG() is true at the Canvas level - allows us to disable
// the png workaround for specific canvii on the fly (EG when printing)
_fixPNG : function () {
    if (this.isPrinting) return false;
    return true;
},

getPrintStyleName : function () {
    return this.printStyleName || this.styleName;
},

// getPrintTagStart / end -- returns the DIV / SPAN tags written out around our HTML in printing
// mode.
getPrintTagStart : function (absPos) {
    var props = this.currentPrintProperties,
        topLevel = props.topLevelCanvas == this,
        inline = !topLevel && !absPos && props.inline,
        className = this.getPrintStyleName();

    var groupTagStart;
    if (this.isGroup) {
        groupTagStart = this.getPrintHTMLTagStart();
    }

    return [groupTagStart, (inline ? "<span " : "<div "),
            (className ? "class='" + className + "' " : null),
            // could add borders etc here
            this.getPrintTagStartAttributes(absPos),
            ">"].join(isc.emptyString);
},

// If the widget is marked as a group, write "fieldset" tags around the print HTML DIV
// element - this basically matches the appearance of a group.
getPrintHTMLTagStart : function () {
    var groupTagStart = "<fieldset>";
    if (this.groupTitle != null) {
        groupTagStart += "<legend>" + this.groupTitle + "</legend>";
    }
    return groupTagStart;
},

getPrintHTMLTagEnd : function () {
    return "</fieldset>";
},


getPrintTagStartAttributes : function (absPos) {
    if (absPos) {
        return " style='position:absolute;left:" + this.getLeft() + "px;top:"
                + this.getTop() + "px;width:" + this.getWidth() + "px;height:"
                + this.getHeight() + "px;' ";
    // If we have absolutely positioned children:
    // - we're going to have to be relatively positioned so the abs-pos children are
    //   rendered within us
    // - we're going to have to have explicit sizing so we take up the right amount of space
    //   in document flow.
    // Handle this by writing out width/height set as calculated scrollWidth/height.
    
    } else if (this.printChildrenAbsolutelyPositioned) {
        return " style='position:relative;width:" + this.getScrollWidth() +
                "px;height:" + this.getScrollHeight() + "px;background-color:lightblue;' ";
    }

    return null;
},

getPrintTagEnd : function (absPos) {
    var props = this.currentPrintProperties,
        topLevel = props.topLevelCanvas == this,
        inline = !topLevel && !absPos && props.inline;

    var groupTagEnd;
    if (this.isGroup) {
        groupTagEnd = this.getPrintHTMLTagEnd();
    }

    var endTag = (this.wrap == false) ? "</div>" : inline ? "</span>" : "</div>";
    if (this.isGroup) endTag += groupTagEnd;
    return endTag;
},


// Backmask
// --------------------------------------------------------------------------------------------

//>BackMask

makeBackMask : function (props) {
    // in Moz, defer backmask creation until page load.  Otherwise the pre-page load heuristics
    // kick in for the iframe, causing crazy rendering (iframe burns through what it's supposed
    // backmask)
    if (isc.Browser.isMoz && !isc.Page.isLoaded()) {
        this._deferredBackMaskProps = props;
        isc.Page.setEvent("load", this, isc.Page.FIRE_ONCE, "makeBackMask");
        return;
    }
    // Note: there's code in BrowserPlugin.draw() that somewhat hackishly reaches into the
    // _deferredBackMaskProps, so be careful if you make changes to this.
    if (this._deferredBackMaskProps) {
        props = this._deferredBackMaskProps;
        delete this._deferredBackMaskProps;
    }
	this._backMask = isc.BackMask.create(props);
	this.addPeer(this._backMask);
  	this._backMask.setZIndex(this.getZIndex(true)-2);
    this._sizeBackMask();
},
//<BackMask

// Focus Proxy
// --------------------------------------------------------------------------------------------



//>FocusProxy create the focusProxy to manage focus for this Canvas
makeFocusProxy : function () {
    // This is actually an almost trivial wrapper for _makeFocusProxy, allowing us to set a
    // '_makingFocusProxy' flag on this widget, and clear it on return, without having to clear
    // it in every possible return case from the function that does the work

    // Bail if
    // - we're not using focusProxies on this element
    // - we already have a focusProxy written into the DOM
    // - we're already running this method (so a call to getTabIndex() or something else fell back
    //   through to this method)
    // - we're not drawn
    // - we're waiting to create a f.p on page load
    if (!this._useFocusProxy || this._hasFocusProxy || this._makingFocusProxy || !this.isDrawn()
        || this.__delayedMakeFocusProxy != null) return;

    // set a temporary flag that we're in the middle of creating a focusProxy
    this._makingFocusProxy = true;

    this._makeFocusProxy();

    this._makingFocusProxy = null;
},

_makeFocusProxy : function () {
    // We know that the widget's handle is completely drawn at this point - therefore we can
    // use insertAdjacentHTML to write the focusProxy handle next to the widget's handle.
    // Note - if the page isn't loaded, using insertAdjacentHTML() to plug the handle into the
    // DOM can cause crashes on some browsers.
    
    if (!isc.Page.isLoaded() && isc.Browser.isSafari) {
        // call this.getTabIndex() to force auto-allocation of tab index to occur in the
        // expected order
        this.getTabIndex();
        // Delay actually writing out the focusProxy until the page is loaded to avoid problems
        // with manipulating the DOM before page load
        this.__delayedMakeFocusProxy =
            isc.Page.setEvent("load", this, null, "delayedMakeFocusProxy");

        return;
    }

    var tabIndex = this.getTabIndex();
    if (this.isDisabled()) tabIndex = -1;

    if (isc.Browser.isSafari && tabIndex == -1) {
        // In Safari, there's no way to write a (natively) focusable element into the page, and
        // exclude it from the page's tab order.
        // In this case just don't write the focusProxy into the DOM at all, and we'll deal
        // with virtual ISC focus only.
        // Note that this means you can't have a focusable widget with a working accessKey and
        // tabIndex -1 in Safari.
        return;
    }

    // Size the focus proxy to match the canvas unless we're in Safari
    
    var width = (isc.Browser.isSafari ? 1 : this.getViewportWidth()),
        height = (isc.Browser.isSafari ? 1 : this.getViewportHeight());

    var focusProxyString = isc.Canvas.getFocusProxyString(
                            this.getCanvasName(),
                            true,
                            this.getOffsetLeft() - 1, this.getOffsetTop() -1,
                            width, height,
                            this.isVisible(), this._canFocus(),
                            tabIndex, this.accessKey,
                            // this param determines whether the element should handle events
                            // directly, or allow page level EH handling.
                            false,
                            // returns a string causing the ISC level focus to be updated
                            this._getNativeFocusHandlerString(),
                            this._getNativeBlurHandlerString()
        );

    // Insert the focusProxyParent into the DOM in the same scope as the widget's clip handle.
    // Note: we insert AFTER not before the clip handle because redraw (for the special case
    // where we're allowing both children and content) makes the assertion that there's nothing
    // between the canvas's start tag and it's first child's start tag except for 'innerHTML'
    // type content. (If this was the first child of some widget and we inserted the
    // focusProxyParent before the widget's handle, we'd be writing the focus proxy between
    // this widget's start tag and the end of the parent's innerHTML)
    isc.Element.insertAdjacentHTML(this.getClipHandle(), "afterEnd", focusProxyString)

    // For simplicity, hang a flag on the widget saying that it has a focusProxy already.
    // Saves us having to get the F.P. from the DOM to check if it's written out already
    this._hasFocusProxy = true;
},

delayedMakeFocusProxy : function () {
    this.__delayedMakeFocusProxy = null;
    this.makeFocusProxy();
},

//>	@method	Canvas._clearFocusProxy()	(IA)
//		@group	focus
//          Internal Method to clear this widget's "focusProxy" from the DOM.
//<
_clearFocusProxy : function () {

    if (!this._useFocusProxy) return;

    // If there's a pending event to make the focus proxy, clear that out.
    if (this.__delayedMakeFocusProxy != null) {
        isc.Page.clearEvent("load", this.__delayedMakeFocusProxy);
        this.__delayedMakeFocusProxy = null;
    }

    // If we never create focusProxy, bail
    if (!this._hasFocusProxy) return;

    var fpp = this._getFocusProxyParentHandle();
    if (fpp != null) {

        // Note: focusProxyParentHandle has no pointers to this widget (if it did we should clear
        // them now)
        if (isc.Browser.isDOM) {
            // remove object tree from the DOM.
            if (fpp.parentNode) {
                fpp.parentNode.removeChild(fpp);
            } else {
                this.logWarn("Unable clear focusProxy for this widget - element has no parentNode.");
            }

        }

        // and clear the '_focusProxy' property from this widget
        this._focusProxy = null;

    }
    // Clear out our helper '_hasFocusProxy' flag so makeFocusProxy doesn't NoOp in the future.
    this._hasFocusProxy = null;

},
//<FocusProxy


_useAccessKeyProxy : function () {
    var result = (isc.Browser.isChrome || (isc.Browser.isMoz && this._useNativeTabIndex));
    return result;
},
_makeAccessKeyProxy : function () {
    var accessKey = this.accessKey;
    if (!accessKey || !this.isDrawn() || !this._canFocus()) return;
    var handleName = this._getDOMID("focusProxy");
    var proxyString = isc.StringBuffer.concat(
        "<a id='", handleName,
        "' href='javascript:void(0)'",
        (isc.Browser.isChrome ? "' onClick" : "' onfocus"),
        "='var _0=window.", this.getID(), ";if(_0){_0.focus();}' ",
        "accessKey='" + accessKey + "'></a>");
    
    isc.Element.insertAdjacentHTML(this.getClipHandle(), "beforeEnd", proxyString);
    this._accessKeyProxy = isc.Element.get(handleName);
},

_clearAccessKeyProxy : function () {
    var element = this._accessKeyProxy;
    delete this._accessKeyProxy;
    if (element) isc.Element.clear(element);
},


// Drawing children and peers
// --------------------------------------------------------------------------------------------

// Draw all children of this Canvas
_$initial_draw : "initial draw",
drawChildren : function () {

	// if no children defined, simply return true
	if (this.children == null) return true;

	// drawChildren is only safe to call BEFORE this canvas has been drawn
	if (this.isDrawn()) {
		//>DEBUG
		this.logWarn("drawChildren() is only safe to call BEFORE a canvas has been drawn" +
                     this.getStackTrace());
		//<DEBUG
		return;
	}

	//>DEBUG
	if (this.children && this.logIsInfoEnabled(this._$drawing)) {
        this.logInfo("drawChildren(): " + this.children.length + " children", this._$drawing);
    }
	//<DEBUG

	// make sure that everything in the children array is a Canvas, and has us as its parentElement
    this._instantiateChildren();

    // NOTE: this entrypoint needs to be exactly here, because this moment - where the parent's
    // HTML exists in the DOM but no children have been drawn - is the only time you could draw some
    // children before deciding on the size of other children.  Otherwise you'd have to resize the
    // other children after drawing them, potentially causing Canvas redraws/native repaints
    this.layoutChildren(this._$initial_draw);

    if (this.manageChildOverflow) this._suppressOverflow = true;

	// draw all children (unless they have a masterElement, in which case the master will draw them
    // itself)
	for ( var i = 0; i < this.children.length; i++) {
		var child = this.children[i];

		// if the child has a masterElement, it's a peer of another child
		//	the other child will handle drawing it, so skip the draw here.
		if (child.masterElement) continue;

		// NOTE: the only legitimate way in which this child might already have been drawn
        // is via a custom override of layoutChildren() above.  Otherwise all children should be
        // undrawn, since:
        // - everything in the this.children array has gone through addChild(), hence was clear()d
        //   if it drew in another context
        // - this Canvas has never drawn it's children
        // - we don't allow Canvii that have undrawn parents to draw()
        // - we're skipping elements that have been drawn as a peer
        if (!child.isDrawn()) child.draw();
	}

},


_$parentDrawn:"parentDrawn",
_completeChildOverflow : function (children) {
    if (!this.manageChildOverflow) return;

    this._suppressOverflow = null;

    this._browserDoneDrawing(); // allows for easier timing
    //this.getHandle().scrollHeight;

    var count = 0;
    for (var i = 0; i < children.length; i++) {
        var child = children[i];
        if (child != null && child._deferredOverflow) {
            count++;
            child._deferredOverflow = null;
            child.adjustOverflow(this._$parentDrawn);
        }
    }
    //if (count > 0) this.logWarn("completed child overflow for " + count + " children");
},


//>	@method	canvas.predrawPeers()	(A)
//			Draw all peers of this Canvas marked for pre-drawing
//		@group	drawing
//<
predrawPeers : function () {
    if (!this.peers) return;

    for (var i =0; i < this.peers.getLength(); i++) {
        var peer = this.peers[i];
        if (peer._drawBeforeMaster == true) {

    		// if the peer is not a canvas, or doesn't recognize us as its master
    		// call addPeer() to create it and/or add it to our list of peers
    		if (!isc.isA.Canvas(peer) || peer.masterElement != this) {
                this.peers.remove(peer);
    			this.addPeer(peer);
    		}

            if (!peer.isDrawn()) peer.draw();
        }
    }
},

//>	@method	canvas.drawPeers()	(A)
//			Draw all peers of this Canvas
//		@group	drawing
//<
drawPeers : function () {
	// if no peers defined, simply return true
	if (!this.peers) return true;

	//>DEBUG
    if (this.logIsInfoEnabled(this._$drawing)) {
    	this.logInfo("drawPeers(): " + this.peers.length + " peers", "drawing");
    }
	//<DEBUG

	// go in two passes through the peers array
	//	 1) make sure that everything in there is a canvas, and has us as its masterElement
	//			if either is not true, call addPeer() to add it as a proper child
	//	 2) draw everything that hasn't been drawn already

	// rebuild the peers array to ensure that it contains real canvases, and each one
    // has us as its master element
	var oldPeers = this.peers;
	this.peers = [];

	for (var i = 0, peer; i < oldPeers.length; i++) {
		peer = oldPeers[i];

		// if the peer is not a canvas, or doesn't recognize us as its master
		// call addPeer() to create it and/or add it to our list of peers
		if (!isc.isA.Canvas(peer) || peer.masterElement != this) {
			this.addPeer(peer);

		// otherwise, it's already been set up correctly (by a previous call to addPeer())
		//	so we'll just add it back to our peers array (which we cleared out earlier)
		} else {
			this.peers.add(peer);
		}
	}

	// pass 2 -- draw all peers
    // All peers now recognize this as the masterElement - so it can safely handle their drawing
    // (Even if they have a parent element, that will recognize that the peer has a master element
    //  and cede drawing to the masterElement)
	for (i = 0; i < this.peers.length; i++) {
		var peer = this.peers[i];

		// set the peers position if snapTo or snapEdge are set
		if (peer.snapTo || peer.snapEdge) peer._resolvePercentageSize();
        // if the peer is not already drawn, draw it
        if (!peer.isDrawn()) peer.draw();
	}

},


//>	@method	canvas._insertHTML()	(A)
//			Internal routine to insert the HTML for this canvas AFTER the page has loaded
//
//		@group	drawing
//
//		@return	(canvas)	Pointer to this canvas.  Returned so statements like the following will work:
//								var myCanvas = Canvas.newInstance({...}).draw()
//
//<
_insertHTML : function (includeInnerHTML, givenDrawContext) {

    // mark that we've starting drawing
    this.setDrawnState(isc.Canvas.DRAWING_HANDLE);

    var innerHTML = includeInnerHTML ? this._getInnerHTML() : null,

    
        buffer = this.getTagStart(true),
        gotArray = isc.isAn.Array(buffer),
        HTML;

    if (gotArray) {
        var origLength = buffer.length;

        buffer[buffer.length] = innerHTML;
        buffer[buffer.length] = this.getTagEnd();
        HTML = buffer.join(isc._emptyString);
        buffer.length = origLength;
    } else {
        HTML = isc.SB.concat(buffer, innerHTML, this.getTagEnd());
    }

    var newElement;
    var logEnabled = this.logIsInfoEnabled(this._$drawing);
    // if a specific DOM insertion position was specified
    var drawContext = givenDrawContext || this.drawContext;
    if (drawContext) {
        var element = drawContext.element,
            position = drawContext.position || "beforeBegin";
		//>DEBUG
        this.logInfo("_insertHTML(): drawing with " + position +
                     " relative to element: " + this.echoLeaf(element), "drawing");
        //<DEBUG

        if (position == "replace") {
            // insert before, then remove
            position = "beforeBegin";
            if (isc.isA.String(element)) element = isc.Element.get(element);
            newElement = this._insertAdjacentHTML(element, position, HTML, true);
            element.parentNode.removeChild(element);

            // drop drawContext (and htmlElement [same reason]);
            this.drawContext = null;
            if (this.htmlElement) this.htmlElement = null;
        } else {
            newElement = this._insertAdjacentHTML(element, position, HTML, true);
        }

    // if there's a drawn master Canvas, draw next to it
    // Note: peer vs master draw order
    // There are cases where we wish to draw a peer before we draw a master element into the
    // DOM - the specific example is the StretchImgButton, where in order to make the button
    // images auto-size to an overflow:visible label, we need to first draw the label out and
    // determine its drawn size before calling the 'getInnerHTML()' method.
    // In order to handle these cases we've introduced the concept of marking some peers for
    // 'predrawing' - which forces them to be written out before their masters (when draw() is
    // called on the master widget).
    // TODO: to prevent accidental out of order drawing, log an error if a peer draws before
    // its master and hasn't been explicitly marked for predrawing.
    
    } else if (this.masterElement && (this.masterElement.getClipHandle() != null)) {
        //>DEBUG
        if (logEnabled) {
            this.logInfo("inserting HTML next to master element: " + this.masterElement,
                         "drawing");
        } //<DEBUG

        // insert our HTML next to our masterElement in the document flow
        var master = this.masterElement.getClipHandle();
    	newElement = this._insertAdjacentHTML(master, this._$afterEnd, HTML, true);

    // if there's a parent Canvas
    } else if (this.parentElement) {

        //>DEBUG
        if (logEnabled) {
            this.logInfo("inserting HTML into parent: " + this.parentElement,
                         "drawing");
        } //<DEBUG
        // insert our HTML within our parent's HTML
        var parent = this.parentElement.getHandle();
    	newElement = this._insertAdjacentHTML(parent, this._$beforeEnd, HTML, true);

    } else {
        // there is no parent Canvas, this is a top-level (absolute) Canvas.  Insert the HTML at
        // the end of the <BODY> tag to create a top-level element

        //>DEBUG
        if (logEnabled) {
            this.logDebug("inserting HTML at top level", "drawing");
        } //<DEBUG
        newElement = this._createAbsoluteElement(HTML);
    }

    
    if (newElement != null) {
        if (this._drewClipDiv) {
            //this.logWarn("caching handle: " + this.echoLeaf(newElement));
            this._clipDiv = newElement;
            this._handle = newElement.firstChild;
        } else {
            this._handle = newElement;
        }
    }

    this.setDrawnState(isc.Canvas.HANDLE_DRAWN);
},


_createAbsoluteElement : function (html) {
    return this.ns.Element.createAbsoluteElement(html);
},
_insertAdjacentHTML : function (element, position, html, singleElement) {
    return this.ns.Element.insertAdjacentHTML(element, position, html, singleElement);
},

// _completeHTMLInit() : helper method for draw() and _insertHTML()
//
// Finishes up the drawing process
//
//  - Assumes we have already drawn the handle into the DOM, and called this.drawChildren()
//
//  - Sets up events
//  - calls this.drawPeers()
//  - marks as drawn and not dirty
//  - calls adjustOverflow
_completeHTMLInit : function () {

    // opportunity to modify content before overflow is adjusted
    this.modifyContent();

    // Moz strict / transitional mode requires explicit sizing of iframe if present
    // (100%/100% not respected)
    // Also true with Chrome / HTML5 mode
    if ((isc.Browser.isSafari || isc.Browser.isMoz) && isc.Browser.isStrict && this.containsIFrame()) this._sizeIFrame();

    
    if (this.manageChildOverflow && this.children != null) {
        this._completeChildOverflow(this.children);
    }

	// set up the handle for the canvas
	this.setUpEvents();

    // If a resize was attempted while the handle was being written out, resize the actual
    // handle
    if (this._resizeHandleOnDrawComplete) {
        // actually resize the handle by calling _setHandleRect
        this._setHandleRect(this.left, this.top, this.width, this._height);
        // if we have a clip region set, it will have been clobbered by _setHandleRect.
        // restore it:
        // Note: already modified this._clip
        var clip = this._clip;
        if (isc.isAn.Array(clip)) this.setClip(clip);

        // AdjustOverflow will get called below
    }

	// mark that we've been drawn successfully and that we're not dirty
    this.setDrawnState(isc.Canvas.COMPLETE);
	this._dirty = false;

    this._updateHandleDisplay();

    // If we don't have a parentElement, add to the list of top level canvii
    if (this.parentElement == null) isc.Canvas._addToTopLevelCanvasList(this);

	// adjust according to our overflow property
    
    if (this.parentElement != null && this.parentElement._suppressOverflow) {
        this._deferredOverflow = true;
    } else {
        this.adjustOverflow(this._$draw);
    }

	// if we have any peers defined, draw them now
    
	this.drawPeers();
},

//> @method canvas.setHtmlElement()
// Setter for the +link{canvas.htmlElement}.
// @param element (DOM element) New htmlElement for this canvas, or Null to clear the existing
//      htmlElement
// @group htmlElement
// @visibility external
//<
setHtmlElement : function (element) {
    if (this.htmlElement == element) return;
    this.htmlElement = element;
    if (!this.htmlPosition) this.htmlPosition = "afterBegin";
    var context = element ? {position:this.htmlPosition, element:this.htmlElement}  : null;
    // setDrawContext will handle clearing / drawing / etc.
    this.setDrawContext(context);
},

//> @method canvas.setHtmlPosition()
// Setter for the +link{canvas.htmlPosition}.
// @param element (DrawPosition) New htmlPosition for this canvas
// @group htmlElement
// @visibility external
//<
setHtmlPosition : function (position) {
    if (position == null) position = "afterBegin";
    if (this.htmlPosition == position) return;

    this.htmlPosition = position;
    if (this.htmlElement == null) return;
    var context = {position:this.htmlPosition, element:this.htmlElement};

    // setDrawContext will handle clearing / drawing / etc.
    this.setDrawContext(context);

},

// Redrawing
// --------------------------------------------------------------------------------------------

//>	@method	canvas.isDirty()	(A)
// Returns whether a canvas is waiting to be redrawn. Will return true if
// +link{canvas.markForRedraw()} has been called, but this canvas has not yet been
// redrawn.
// @group drawing
// @return (Boolean) true is this canvas needs to be redrawn; false otherwise
// @visibility external
//<
isDirty : function () {
	return this._dirty == true;
},


//>	@method	canvas.markForRedraw()  ([])
// Marks the widget as "dirty" so that it will be added to a queue for redraw. Redraw of dirty
// components is handled by a looping timer and will after a very short delay (typically less than
// 100ms). In most cases it is recommended that developers use <code>markForRedraw()</code>
// instead of calling +link{canvas.redraw()} directly. Since this method queues the redraw, multiple
// calls to markForRedraw() within a single thread of execution will only lead to a single DOM
// manipulation which greatly improves application performance.
//
//  @visibility external
//  @group  drawing
//  @param  [reason]    (string : "no reason provided") reason for performing the redraw
//<
markForRedraw : function (reason) {

    if (isc._traceMarkers) arguments.__this = this;

	// If we've been drawn already, add this to the queue of items to be redrawn automatically.
    // If we're already dirty, we're already in the redraw list.
	if (this.isDrawn() && !this.isDirty()) {
        this._logRedraw(reason);
		isc.Canvas.scheduleRedraw(this);

	    // mark the item as dirty so we don't schedule the event again later
	    this._dirty = true;
	}
},

readyToRedraw : function (reason, askedToRedraw) {
    if (isc._traceMarkers) arguments.__this = this;

    if (!this.isDrawn()) {
    	
        return false;
    }

    // NOTE: unsafe times to redraw
    // When we redraw a widget, we throw away the old HTML and replace it with new HTML - a
    // subtle consequence of this is that the DOM object that is the target of the current event
    // is *destroyed*.  Various browsers on various platforms while processing particular types
    // of events with various HTML contents react badly to this.
    //
    // So now, whenever there is an attempt to redraw during mouseDown or mouseUp, we delay the
    // redraw regardless of the browser, since we would prefer not to learn about the rest of
    // the obscure cases where an immediate redraw won't work.
    //
    // However, postponing this redraw means that this redraw, which the caller wanted to happen
    // immediately, will be done in a batch of redraws, which means no actual native repaint
    // will occur until every widget that needs to redraw is done redrawing, which might take a
    // while.  So we set a "priority redraw" flag so the redraw of this object is done in a
    // batch of its own.
    
    var EH = this.ns.EH;
    if (EH.lastTarget == this &&
        (EH._handlingMouseUp || EH._handlingMouseDown ||
         (isc.Browser.isMobileWebkit && EH.dragOperation == EH.DRAG_SCROLL)
        )
       )
    {
        if (askedToRedraw) {
            // if actually asked to redraw, schedule for later
            this._logRedraw(reason, true);
            this.priorityRedraw = true;
            // Mark as non-dirty (otherwise markForRedraw is a no-op)
            this._dirty = false;
            this.markForRedraw(false);
        }

        return false;
    }

    return true;
},

_logRedraw : function (reason, type) {
    //>DEBUG

    // NOTE: some callers pass reason = false to suppress this log, since the redraw is already
    // logged elsewhere (clearRedrawQueue and redrawChildren)
    if (reason == false || !this.logIsInfoEnabled(this._$redraws)) return;

    // log a stack if 'redrawTrace' log is enabled, or in debug mode and no reason was provided
    var logTrace = (!reason && this.logIsDebugEnabled(this._$redraws) ||
                    this.logIsDebugEnabled("redrawTrace"));

    var message;
    if (type == null) message = "Scheduling redraw ";
    else message = (type == true ? "DEFERRED " : "") + "Immediate redraw ";

    this.logInfo(message +
                 // log that widget is dirty only if an immediate redraw was requested (whether
                 // it was deferred or not)
                 (this.isDirty() && type != null ? "of dirty widget " : "") +
                 // redrawing with children indicates all children redraw
                 (this.children && this.children.length > 0 ?
                  "(" + this.getChildCount() + " children) " : "") +
                 // reason for redraw
                 "(" + (reason ? reason : "no reason provided") + ")" +
                 // log trace
                 (logTrace ? this.getStackTrace() : ""),
                 this._$redraws);
    //<DEBUG
},

//>	@method	canvas.redraw() ([A])
// Redraws the widget immediately with its current property values.
//
// Generally, if you want a Canvas to redraw, call markForRedraw() - this will cause the Canvas to
// be redrawn when current processing ends, so that a series of modifications made to a Canvas will
// cause it to redraw only once.
//
// Only call redraw() directly if you need immediate responsiveness, for example you are redrawing
// in response to continuous mouse motion.
//
//  @visibility external
//  @group  drawing
//  @param  [reason]    (string : "no reason provided") reason for performing the redraw
//<
//
// NOTE: this does not necessarily have to redraw all of the HTML of a Canvas.  Subclasses can
// override redraw to do something smarter.
redraw : function (reason) {
    if (isc._traceMarkers) arguments.__this = this;

    if (!this.readyToRedraw(reason, true)) return this;

    //>DEBUG
    this._logRedraw(reason, false);
    // track total redraws
    this._addStat(this._$redraws);

    
    //<DEBUG

    var start = isc.timeStamp();
    

    this._updateHTML();
    

    // track last redraw time
    this._lastRedrawTime = isc.timeStamp() - start;

	return this;
},

redrawIfDirty : function (reason) {
    if (this.isDrawn() && this.isDirty()) return this.redraw(reason);
},

//>	@method	canvas._updateHTML()	(A)
//		Redraw an existing layer by generating new HTML and replacing the existing HTML.
//
//      NOTE: non-framework code should call redraw(), not this method
//
//		@group	drawing
//
//		@return	(canvas)	Pointer to this canvas.  Returned so statements like the following will
//		                    work:
//								var myCanvas = Canvas.newInstance({...}).draw()
//<
_updateHTML : function () {

    //>DEBUG
    var logDebugEnabled = this.logIsDebugEnabled(this._$drawing),
        logInfoEnabled = this.logIsInfoEnabled(this._$drawing),
        startTime;
    if (logDebugEnabled) startTime = isc.timeStamp();
    if (logInfoEnabled) this.logInfo("_updateHTML(): redrawing", "drawing");
    //<DEBUG

    // if we have any peers, call the redrawPredrawnPeers() method to check for any peers marked
    // for drawing before the master element, and redraw them first.
    // (See addPeer() and predrawPeers() methods for more info).
    if (this.peers != null && this.peers.getLength() > 0) this.redrawPredrawnPeers();

    var hasChildren = this.children && this.children.length > 0,
        childrenAndContent = this.allowContentAndChildren && hasChildren;

    // special case: if shouldRedrawOnResize() assume redraw should always redraw content - if
    // we have children, flip on 'allowChildrenAndContent'
    
    if (hasChildren && !childrenAndContent && this.shouldRedrawOnResize()) {
        childrenAndContent = true;
    }

    
    if ((!hasChildren || childrenAndContent) &&
        (this.getVisibleWidth() > this.getWidth() ||
         this.getVisibleHeight() > this.getHeight()))
    {
        //this.logWarn("resizing overflow Canvas before redraw");
        if (this.notifyAncestorsOnReflow && this.parentElement != null) {
            this.notifyAncestorsAboutToReflow();
        }
        this._setHandleRect(null, null, this.width, this._height);
    }

    if (hasChildren) {
        // update the HTML that came from parent.getInnerHTML().
        if (childrenAndContent) this._updateParentHTML();

        this.redrawChildren();
    } else {
        // childless - update inner HTML
        this._updateInnerHTML();
    }

    // If we're writing out a placeholder div to enforce scroll-size, and we just
    // redraw our innerHTML, reapply it now.
    
    if (this._enforcingScrollSize && !hasChildren) {
        // The old div will no longer be present, so if this._scrollSizeDiv (if set)
        // will be out of date (pointing to an element that's no longer written into the DOM).
        // Clear the pointer out to ensure we create a fresh div.
        delete this._scrollSizeDiv;
        this.enforceScrollSize(this._enforcingScrollSize[0], this._enforcingScrollSize[1]);
    }

    // opportunity to modify new content before overflow is adjusted
    this.modifyContent();

    // set up events in the handle
    this.setUpEvents();

    // mark this item as not dirty any more
    
    this._dirty = false;
    // adjust the overflow again
    this.adjustOverflow(this._$redraw, null, true);

    // if we have any peers, redraw them
    
    this.redrawPeers();

    
    //>DEBUG
    if (logDebugEnabled) {
        this.logDebug("Redraw() - Total time to redraw in DOM:" + (isc.timeStamp() - startTime),
                      "drawing");
    }
    //<DEBUG

    if (this.notifyAncestorsOnReflow && this.parentElement != null) {
      	this.notifyAncestorsReflowComplete();
    }

    // return "this" for chaining calls (canvas.redraw().moveTo(..).. )
    return this;
},

// If this widget is overflow:"visible", on redraw, we force a handle resize to specified size
// before refreshing content.
// This may causes us to shrink, changing the scroll-size of our ancestors.
// If they are scrolled, this can cause a native scroll-back to 0,0.
// The "notifyAncestorsOnReflow" flag is a way to workaround this - in this case we notify
// our parents before changing our size allowing them to remember their scroll position, then
// when the redraw completes, the parents are notified again, allowing them to bounce back to
// the original scroll position if possible.

notifyAncestorsOnReflow:true,

notifyAncestorsAboutToReflow : function () {
    if (this.parentElement) this.parentElement._childAboutToReflow(this);
},

notifyAncestorsReflowComplete : function () {
    if (this.parentElement) this.parentElement._childReflowComplete(this);
},

_childAboutToReflow : function (child) {
    if (this.overflow != isc.Canvas.VISIBLE) {
        this._preReflowScrollTop = this.getScrollTop();
        this._preReflowScrollLeft = this.getScrollLeft();
        // Suppress adjustOverflow - we don't want to temporarily hide scrollbars etc.
        this._suppressAdjustOverflow = true;
    // Note that if we're overflow:visible, the child reflowing may cause our size to change as well
    // so we have to notify our ancestors.
    } else {
	    if (this.parentElement) this.parentElement._childAboutToReflow(child);
	}
},

_childReflowComplete : function (child) {
    if (this.overflow != isc.Canvas.VISIBLE && this._suppressAdjustOverflow) {
        delete this._suppressAdjustOverflow;
        var shouldScroll = false,
            scrollLeft, scrollTop;
        if (this._preReflowScrollTop != null && this._preReflowScrollTop != this.getScrollTop()) {
            shouldScroll = true;
            scrollTop = this._preReflowScrollTop;
            delete this._preReflowScrollTop;
        }
        if (this._preReflowScrollLeft != null && this._preReflowScrollLeft != this.getScrollLeft()) {
            shouldScroll = true;
            scrollLeft = this._preReflowScrollLeft;
            delete this._preReflowScrollLeft;
        }
        if (shouldScroll) {
            this.scrollTo(scrollLeft, scrollTop,
                "Reset scroll position for child content reflow");
        }
    } else {
	    if (this.parentElement) this.parentElement._childReflowComplete(child);
	}
},

// update the HTML of a parent without changing the HTML of its children

_updateParentHTML : function () {
    var innerHTML = this._getInnerHTML(),
        thisHandle = this.getHandle();

    // We achieve this by removing all the text inside the content handle for the
    // widget up to the handle of the first child, and then inserting the new innerHTML
    // in the same place
    //
    // Note: within the content handle for a canvas, we will always have the innerHTML
    // (returned from getInnerHTML()) for the canvas, followed by the child nodes.
    // Therefore everything up to the first child's handle is the parents content (this
    // assumption could only be violated by unsupported manual DOM manipulation).

    

    // remove everything up to the first Canvas.  NOTE: we can't compare against
    // this.children[0].getHandle() because our children may draw out of order, get clear()d,
    // etc.
    while (thisHandle.hasChildNodes()) {
        var eventProxy = thisHandle.firstChild.getAttribute ?
                         thisHandle.firstChild.getAttribute(this._$eventProxy) : null;
        if (eventProxy && isc.isA.Canvas(window[eventProxy])) break;
        //this.logWarn("removing element: " + this.echoLeaf(thisHandle.firstChild));
        thisHandle.removeChild(thisHandle.firstChild);
    }
    // add the parent's new HTML
    isc.Element.insertAdjacentHTML(thisHandle, this._$afterBegin, innerHTML);
},
_$eventProxy : "eventProxy",

// update the innerHTML of a childless Canvas
_updateInnerHTML : function () {
    var wasPrinting = this.isPrinting;
    this.isPrinting = false;

	var innerHTML = this._getInnerHTML();
    this.getHandle().innerHTML = innerHTML;

    this.isPrinting = wasPrinting;
},

// opportunity to modify drawn or redrawn content before overflow is adjusted
modifyContent : function () {},

//>	@method	canvas.redrawChildren()	(A)
//		Redraw all of our children
//		@group	drawing
//<
redrawChildren : function () {
	// if no children defined, simply return true
	if (! this.children) return true;

	//>DEBUG
    this.logInfo("redrawChildren(): " + this.children.length + " children", "drawing");
	//<DEBUG

	// redraw each child
	for (var list = this.children, i = 0; i < list.length; i++) {
		var child = list[i];

		if (!isc.isA.Canvas(child)) continue;
		if (child._redrawWithParent) {
			child.redraw(false);
		}
	}
},

//>	@method	canvas.redrawPredrawnPeers()	(A)
//		Redraw any of our peers marked for preDraw via the '_drawBeforeMaster' flag
//		@group	drawing
//      @see    predrawPeers()
//      @see    addPeer()
//<
redrawPredrawnPeers : function () {
    // bail if we have no peers.
    if (!this.peers || this.peers.getLength < 1) return;

	// draw each peer marked for predrawing
	for (var list = this.peers, i = 0; i < list.length; i++) {
		if (list[i] && list[i]._redrawWithMaster && list[i]._drawBeforeMaster) {
			list[i].redraw("redrawPeers");
		}
	}

},


//>	@method	canvas.redrawPeers()	(A)
//      Redraw all of our peers (excluding those marked for drawing / redrawing before their
//      master element)
//      @group	drawing
//<
redrawPeers : function () {
	// if no peers defined, simply return true
	if (!this.peers) return true;

	//>DEBUG
    this.logInfo("redrawPeers(): " + this.peers.length + " peers", "drawing");
	//<DEBUG

	// redraw each peer
	for (var list = this.peers, i = 0; i < list.length; i++) {
		if (list[i] && list[i]._redrawWithMaster && !list[i]._drawBeforeMaster) {
			list[i].redraw("redrawPeers");
		}
	}
},

// Update / Refresh / Replace from server
// --------------------------------------------------------------------------------------------

//>	@method	canvas.updateFromServer()	(A)
//
// A flexible way to update a component from the server.
// <p>
// Makes a request to the server at the URL specified by the actionURL of the provided RPCRequest.
// Sets +link{attr:RPCRequest.evalResult} and +link{attr:RPCRequest.suppressAutoDraw} to true in
// the provided request and automatically makes available the component on which this method is
// called under the name 'targetComponent' in the response received from the server (i.e. in the
// +link{attr:RPCRequest.evalVars} of the request).
// <p>
// For example, let's say you wanted to add a component available from the server at the URL
// '/myComponent.jsp' to a layout on the current page called 'myLayout'.  You can accomplish this by
// calling this on the client:
// <pre>
// myLayout.updateFromServer({actionURL: "/myComponent.jsp"});
// </pre>
// In the body of myComponent.jsp you could then do e.g:
// <pre>
// var newComponent = Label.create({contents: "hello world"});
// targetComponent.addMember(newComponent);
// </pre>
// The URL targeted by updateFromServer must produce valid JavaScript code, but how that happens is
// up to you - this can be a static file, a JSP or a Servlet.
// <p>
// Note that you can use all other features of +link{class:RPCRequest} as part of
// updateFromServer().  For example, if you wanted show a prompt with the contents "loading
// component" while the update is in progress and get a callback when it completes while also
// sending some parameters to the server that would be available via request.getParameter() in
// e.g. your JSP, you can issue the above request as follows:
// <pre>
// myLayout.updateFromServer({
//     actionURL: "/myComponent.jsp",
//     showPrompt: true,
//     prompt: "loading component",
//     params : {
//         "componentId": myLayout.getID(),
//         "foo": "bar"
//     },
//     callback: "alert('done updating "+myLayout.getID()+"')"
// });
// </pre>
//
// @param rpcRequest (RPCRequest) minimally must specify the actionURL, but all other RPCRequest
//                                features are available.
//
// @visibility internal
//<
updateFromServer : function(request) {
    // make a copy so we don't modify user's object
    request = isc.clone(request);
    isc.addProperties(request, {
        
        useXmlHttpRequest: true,
        evalResult : true,
        suppressAutoDraw : true
    });

    // this component is autogically made available as part of the evalVars
    if(!request.evalVars) request.evalVars = {};
    if(!request.evalVars.targetComponent) request.evalVars.targetComponent = this;

    isc.rpc.sendRequest(request);
},


refreshFromServer : function(url, data, prompt, callback) {
	this._refreshOrReplaceFromURL("refresh", url, data, prompt, callback);
},

replaceFromServer : function(url, data, prompt, callback) {
	this._refreshOrReplaceFromURL("replace", url, data, prompt, callback);
},



_refreshOrReplaceFromURL : function(action, url, data, prompt, callback) {
    // don't more than one attempted refresh/replace, because we don't want to write a bunch of
    // logic to guarantee the correct order (i.e. server may respond to the second request before
    // the first)
	if (this._refreshing) {
        this.logWarn("Attempt to "+action+" while "+this._refreshAction+" is in progress - ignoring.");
        return;
    }
    this._refreshing = true;
	this._refreshAction = action;
    this._refreshCallback = callback;

    

	this.logDebug("Submitting to " + action + " URL: " + url + ", with data: " + this.echo(data));

    isc.Comm.sendFieldsToServer({
        URL:url,
        fields:data,
        prompt:prompt,
        callback: this.getID() + "._refreshReply(frame)",
        resultVarName: this.refreshVariable
    });
},

_refreshReply : function (frame) {
    // release the refresh lock
    this._refreshing = false;

	var action = this._refreshAction;
    var newConfig = frame[this.refreshVariable];

    if (!isc.isAn.Object(newConfig)) {
        this.logError("Expected object literal for " + action +
                      ", but got: " + isc.Log.echo(newConfig));
        return;
    }
    // need to clone the newConfig since it came from another frame
    newConfig = isc.clone(newConfig);

    var visibleInstance = this;
	// if it's a refresh just setProperties on the existing object
	if (action == "refresh") this.setProperties(newConfig);
	else { // it's a replace
		// if a constructor property was not passed in for the item that we're replacing this Canvas
		// with, assume it's going to be of the same type as whatever it is replacing.
		if (!newConfig._constructor) newConfig._constructor = this.getClassName();
		visibleInstance = this.replaceWith(newConfig);
	}

    // possibly call after callback?
	isc.clearPrompt();

    // call the registered callback, if any
    if (this._refreshCallback) {
        if (!isc.isA.Function(this._refreshCallback)) {
            this._refreshCallback = isc.Func.expressionToFunction("canvas", this._refreshCallback);
        }
        // make sure the conversion worked
        if (!isc.isA.Function(this._refreshCallback)) {
            this.logError("Can't convert "+action+" callback '"+this._refreshCallback
                          +" to a function - not firing callback!");
            return;
        }
        this._refreshCallback(visibleInstance);
    }

},

// Clear and Destroy
// --------------------------------------------------------------------------------------------

//>	@method	canvas.clear() [A]
// Remove all visual representation of a Canvas, including all child or member Canvases, or
// managed top-level components such as the ListGrid drop location indicator.
// <P>
// This is more expensive than hide(), because in order to become visible again, the Canvas must
// be draw()n again.  Generally, application code has no reason to call clear() unless it is
// attempting to do advanced memory management.  If you want to temporarily hide a Canvas, use
// hide() and show(), and if you want to permanently destroy a Canvas, use +link{destroy()}.
// <P>
// You would only use clear() if you were managing a very large pool of components and you
// wanted to reclaim some of the memory used by components that had not been used in a while,
// while still being able to just draw() them to make them active and visible again.
// <P>
// Note: a clear() will happen as part of moving a Canvas to a different parent.  See
// +link{addChild()}.
//
// @visibility external
//<
clear : function (dontReport) {
    this._clearing = true;

	//>DEBUG
    // increment total clears (if not called from parent or as part of destroy)
    if (!dontReport && this.logIsInfoEnabled("clears")) {
        var message = "clear()" +
                      // clearing with children indicates all children clear (without
                      // individually reporting it)
                      (this.children && this.children.length > 0 ?
                       " (" + this.getChildCount() + " children) " : "") +
                      (this.logIsDebugEnabled("clears") ? this.getStackTrace() : "");

        // NOTE: in the log, we only report the first call to clear(), but for the stat, we
        // report each individual handle clear (from clearHandle())
        this.logInfo(message, "clears");

        
    }
	//<DEBUG

    // blur - don't let undrawn items have focus
    this._updateFocusForHide();

	// remove from EventHandler mask registry if necessary
	// NOTE: This must be called before we clear peers, since unregisterMaskableItem()
	//	will try to destroy the event mask, which is a peer.
	if (this._eventMask) this.ns.EH.unregisterMaskableItem(this);

    //>EditMode if we had the resize thumbs, hide them
    if (this == isc.Canvas._thumbTarget) isc.Canvas.hideResizeThumbs();
    //<EditMode

	// clear the handle for this widget
    
	//if (!this._clearedByParent && this.getHandle()) this.clearHandle();

    //>FocusProxy If we have a focusProxy, clear it from the DOM as well.
    if (this._useFocusProxy) this._clearFocusProxy();
    //<FocusProxy

	// tell all of our children to clear so they clean up their own act
	if (this.children) {
		for (var list = this.children, i = 0; i < list.length; i++) {
			var child = list[i];
			if (!isc.isA.Canvas(child)) continue;
			child._clearedByParent = true;
			child.clear(true);
			child._clearedByParent = null;
		}
	}

	if (this.getHandle()) this.clearHandle();

    // don't send notifications to parents that are destroying themselves anyway
    if (this.parentElement) this.parentElement.childCleared(this);
    if (this.masterElement) this.masterElement.peerCleared(this);

    // Clear the scroll-size enforcer div if present
    // (Don't call stopEnforcing - if we get drawn again, continue to enforce the scrollSize)
    delete this._scrollSizeDiv;

    // clear the pointer to the accessKeyProxy element, if there is one
    delete this._accessKeyProxy;

    

    // clear any delayed draw event
    if (this.deferredDrawEvent) {
        isc.Page.clearEvent(this.deferredDrawEvent);
        delete this.deferredDrawEvent;
    }

	// if we have any peers, clear them as well
	if (this.peers) {
		for (var list = this.peers, i = 0; i < list.length; i++) {
            // if we were cleared by our parent, our peers were too
            if (this._clearedByParent) list[i]._clearedByParent = true;
			list[i].clear(true);
			list[i]._clearedByParent = null;
		}
	}

	// note that we're no longer droppable
	if (this.canAcceptDrop) this.ns.EH.unregisterDroppableItem(this);

	// and note that we're no longer drawn
	this.setDrawnState(isc.Canvas.UNDRAWN);

    
    delete this._setToDisplayNone;
    delete this._visibleDisplayStyle;

    
	this._$leftCoords = this._$topCoords = null;

    delete this._clearing;
},


//>	@method	canvas.destroy()	(A)
// Permanently destroy a Canvas and all of it's children / members, recursively.
// <P>
// Like +link{clear()}, calling <code>destroy()</code> removes all HTML for the component;
// unlike clear(), a destroyed Canvas is permanently unusable: it cannot be draw()'n again,
// cannot be referenced by its global ID, and is eligible for garbage collection (assuming
// that application code is not holding a reference to the Canvas).
// <P>
// Any attempt to call a method on a destroyed Canvas will generally result in an error.  If your
// application is forced to hold onto Canvas's that might be destroy()d without warning, you can
// avoid errors by checking for the +link{canvas.destroyed} property.  If you override certain Canvas
// methods, your code may be called while a Canvas is being destroy()d; in this case you can avoid
// extra work (and possibly errors) by checking for the +link{canvas.destroying} property.
// <P>
// Note that <code>destroy()</code> should not be called directly in event handling code for this
// canvas. For this reason, wherever possible we recommend using +link{canvas.markForDestroy()}
// instead of calling this method directly.
//
// @see canvas.markForDestroy()
// @visibility external
//<


//> @attr canvas.destroyed (boolean : null : RA)
// If this property is set to <code>true</code>, the +link{canvas.destroy(), destroy()} method
// has been called on this canvas. This implies the canvas is no longer valid. Its ID has been
// removed from global scope, and calling standard canvas APIs on it is likely to result in
// errors.
// @see canvas.destroy()
// @visibility external
//<

//> @attr canvas.destroying (boolean : null : RA)
// This property is set to true when the +link{Canvas.destroy()} method is called on a widget.
// If this property is true, but +link{Canvas.destroyed} is not, this indicates the canvas is
// in the process of being destroyed.
// @see canvas.destroy()
// @visibility external
//<


destroy : function (indirectDestroy,b,c,d,e) {
    
    if (this.selectionComponent) {
        this.ignore(this.selectionComponent, "selectionChanged");
        this.ignore(this.selectionComponent, "cellSelectionChanged");
    }

    // if we're marked doNotDestroy, just clear()
    if (this.doNotDestroy) {
        this.clear();
        return;
    }

    // if we're already destroyed don't do it again
    if (this.destroyed) return;

    // set a flag so we don't do unnecessary work during a destroy()
    this.destroying = true;
    // shouldn't need to blur, as both 'hide()' and 'clear()' do a blur, and if this isn't
    // drawn it won't have focus

    // remove this widget from the toplevel component's list of local ids
    if (this._screen && this._screen._localIds) {
        for (i in this._screen._localIds) {

            if (this._screen._localIds[i] == this) {
                delete this._screen._localIds[i];
            }
        }
    }

    // clear the overflow timer for delayed adjustoverflow
    if (this._overflowQueued) isc.Timer.clearTimeout(this._overflowTimer);

    // if this widget is showing a clickMask (eg modal Dialog), get rid of it.  This will no-op
    // if this widget is not showing a clickMask.
    this.hideClickMask();

    this._logDestroy(true, indirectDestroy);

    // If we're showing the hover canvas, clear it.
    if (isc.Hover.lastHoverCanvas == this) isc.Hover.hide();

    // destroy our DOM representation.
	this.clear(true);

    // sever parent/peer connection as early as possible to prevent any code that traverses the
    // parent hierarchy from doing extra work
    this.deparent();
    this.depeer();

	// tell all of our children to destroy so they clean up their own act
	if (this.children) {
		for (var list = this.children.duplicate(), i = 0; i < list.length; i++) {
			var child = list[i];
			if (!isc.isA.Canvas(child)) continue;
			
			if (child.destroyWithParent === false) child.deparent();
			else child.destroy(true);
		}
	}

	// if we have any peers, destroy them as well
	if (this.peers) {
		for (var list = this.peers.duplicate(), i = 0; i < list.length; i++) {
			list[i].destroy(true);
		}
	}

	// wipe out our links to our children
	delete this.peers;
	delete this.children;

    // Verify that we have no scrollbars. May not have been caught in the peer class
    // if the scrollbar was never rendered out.
    if (this.hscrollbar && !this.hscrollbar.destroyed) {
        this.hscrollbar.destroy(true);
        delete this.hscrollbar;
    }
    if (this.vscrollbar && !this.vscrollbar.destroyed) {
        this.vscrollbar.destroy(true);
        delete this.vscrollbar;
    }

    // AutoChildren: By default destroy any autochildren we created
    // We set up the _createdAutoChildren object in createAutoChild
    // This is of the format:   {childName:<array of IDs>}
    // Auto destroy these and clear this[childName] at the same time, if appropriate
    if (this._createdAutoChildren) {
        var autoChildren = this._createdAutoChildren;
        for (var childName in autoChildren) {

            var array = autoChildren[childName];
            for (var i = 0; i < array.length; i++) {
                var childID = array[i],
                    child = childID ? window[childID] : null;

                if (child && !child.destroyed && child.destroy && !child.dontAutoDestroy)
                {
                   child.destroy();
                }
            }

            // Always clear out this[childName].
            // Probably not really required but if we didn't destroy the child (dontAutoDestroy)
            // we don't want to keep pointing to it
            delete this[childName];
        }
    }

    // if we have an event proxy, or any other widgets are event proxies for this one, clear
    // out the references in both directions.
    if (this.eventProxy != null) this.clearEventProxy();
    if (this._proxiers != null) {
        for (var list = this._proxiers.duplicate(), i = 0; i < list.length; i++) {
            list[i].clearEventProxy();
        }
    }

    if (this.locatorParent && this.locatorParent.locatorChildDestroyed) {
        this.locatorParent.locatorChildDestroyed(this);
    }
    delete this.locatorParent;

	// remove ourselves from the canvas list
	this._canvasList();

    // remove ourselves from the top-level canvas list
    isc.Canvas._removeFromTopLevelCanvasList(this);

    // remove ourselves from the tab-order management system
    this._removeFromAutoTabOrder();

    // notify the EH that we've been destroyed so it can clear up any pointers it has to us
    isc.EH.canvasDestroyed(this);

    // clear our global ID (removes the window.ID pointer to us)
    isc.ClassFactory.dereferenceGlobalID(this);

    // If we have been notified of anything pointing to this object, remove that pointer.
    
    if (this.pointersToThis != null) {
        for (var i = 0; i < this.pointersToThis.length; i++) {
            var pointer = this.pointersToThis[i];
            if (pointer.object && (pointer.object[pointer.property] == this)) {
                // NOTE: don't use 'delete', as this crashes on the global window object in IE
                var undef;
                pointer.object[pointer.property] = undef;
            }
        }
        // Clear up our pointers in the other direction.
        delete this.pointersToThis;
    }

    // delete all properties
    
    if (this._deletePropsOnDestroy) {
        for (var prop in this) {
            delete this[prop];
        }
    }

    // release the unique IDs we generated for our DOM element(s) so they can be reused by other
    // widgets
    
    this._releaseDOMIDs();

    // set a destroyed flag so that if someone still has a pointer to this widget, they can tell
    // it's destroyed
    this.destroyed = true;

    
    this.invokeSuper(isc.Canvas, "destroy", indirectDestroy,b,c,d,e);
},

//> @method canvas.markForDestroy()
// +link{canvas.destroy(),destroy()} this canvas on a timeout. This method should be used instead of
// calling <code>canvas.destroy()</code> directly unless there's a reason a the canvas needs to be
// destroyed synchronously. By using a timeout, this method ensures the <code>destroy()</code> will
// occur after the current thread of execution completes. This allows you to easily mark canvases
// for destruction while they're handling events, which must complete before the canvas can be
// destroyed.
// @see canvas.destroy()
// @visibility external
//<
markForDestroy : function () {

    if (isc._traceMarkers) arguments.__this = this;

    if (this.destroyed || this.destroying || this.isPendingDestroy()) return;
    this._pendingDestroy = true;

    this._logDestroy(false, false);
    isc.Canvas.scheduleDestroy(this);
},

isPendingDestroy : function () {
    return !this.destroyed && !this.destroying && (this._pendingDestroy == true);
},

_logDestroy : function (synchronous, indirectDestroy) {
    //>DEBUG
    if (this._iscInternal) return;
    // report every destroy for stats, but only log the direct destroys when
    // parents/masters destroy children/peers.
    if (synchronous) this._addStat("destroys");
    if (!indirectDestroy && this.logIsInfoEnabled("destroys")) {
        this.logInfo((synchronous ? "destroy()" : "markForDestroy()") +
                     (this.children && this.children.length > 0 ?
                      " (" + this.getChildCount() + " children) " : "") +
                     (this.logIsDebugEnabled("destroys") ? this.getStackTrace() : ""),
                     "destroys");
    }
    //<DEBUG
},

//>	@method	canvas.clearHandle()	(A)
//		Clear the canvas handle to free up memory (as much as we can anyway).
//		Note: this can assume that there actually is a handle.
//		@group	handles
//<
clearHandle : function (useRemoveChild) {
    
    // if we don't have a handle now, we've probably already been cleared...
    if (!this.getHandle()) return;

    // report each individual handle clear for the stat (only actual calls to clear() are
    // logged)
    this._addStat("clears");

    
    this.getHandle().eventProxy = null;
    this.getClipHandle().eventProxy = null;

    // If we have both a content element and a clip element, the clip element is the outer
    // element, so it's the one to destroy
    var handle = this.getClipHandle();

    // and clear our ref's to the handle so we don't try to access it again
    this._handle = null;
    this._styleHandle = null;
    this._clipDiv = null;

    // use 'Element.clear()' to actually erase the handle from the DOM.

    

    
    isc.Element.clear(handle, useRemoveChild || this._clearWithRemoveChild);

    
    handle.onscroll = null;
},

// Replacing / Placing in the DOM
// --------------------------------------------------------------------------------------------

//>	@method	canvas.replaceWith()	(A)
//		Clear this canvas handle, and insert another canvas at the same place in the DOM
//      so if relatively positioned, the document will continue to flow around the replacement
//		@group	handles
//      @param  otherCanvas (widget)    Canvas with which to replace this one.
//<
replaceWith : function (otherCanvas) {
    // bail if passed something that isn't an object
    // (we'll accept either an object literal canvas init descriptor, or a canvas)
    if (!isc.isAn.Object(otherCanvas)) return;

    // -- Get all the information from 'this' we need:

    // In DOM browsers, for relatively positioned Canvii, place a marker in the DOM so that we
    // can put the replacing Canvas in exactly the same spot.  In Non-DOM browsers we can't do
    // a proper replace within the document flow instead we'll just clear this widget, and put
    // the replacement in the same parent.

    var marker;
    if (isc.Browser.isDOM) {

        // place a marker element in the DOM where this element was
        var ID = isc.ClassFactory.getNextGlobalID();
        isc.Element.insertAdjacentHTML(this.getClipHandle(), "afterEnd",
                                     "<DIV ID=" + ID + "></DIV>");
        var marker = this.getDocument().getElementById(ID);

        // pass the marker element to the replacing Canvas to indicate the place where it should
        // draw
        otherCanvas.drawContext = { element:marker };
    }

    // the widget replacing us needs to become a child/peer of our parent/master if we have one
    var parentElement = this.parentElement,
        masterElement = this.masterElement,
        //if the original Canvas was in a Layout, preserve it's position.
        
        inLayout = (isc.isA.Layout(parentElement) && parentElement.hasMember(this)),
        layoutPosition = (inLayout ? parentElement.getMemberNumber(this) : 0);

    // -- Completely remove this canvas
    // Note: We're removing this canvas *before* initializing the other object into a canvas
    // (if it's not already a Canvas) to avoid issues with colliding IDs.
    this.destroy();

    // get rid of the other canvas handle (*if it existed already)
    if (isc.isA.Canvas(otherCanvas)) {
        otherCanvas.clear();
    // If it's just an init block, set autoDraw to false and initialize
    } else {
        otherCanvas.autoDraw = false;

        otherCanvas = isc.ClassFactory.newInstance(otherCanvas);
        if (otherCanvas == null) {
            //>DEBUG
            this.logWarn("canvas.replaceWith(): Unable to create a widget " +
                         "instance from the argument passed in.  Returning.")
            //<DEBUG
            return;
        }
    }

    // draw the replacing Canvas as a Layout member, child, peer, or top-level widget according
    // to the replaced Canvas
    if (inLayout) {
        parentElement.addMember(otherCanvas, layoutPosition)
    } else if (parentElement) {
        parentElement.addChild(otherCanvas)
    } else if (masterElement) {
        masterElement.addPeer(otherCanvas)
    }

    if (!otherCanvas.isDrawn()) otherCanvas.draw();

    if (isc.Browser.isDOM) {
        // Remove the marker so it doesn't clutter up the document
        if (marker.parentNode) {
            marker.parentNode.removeChild(marker)
        } else {
            //>DEBUG
            this.logWarn("unable to clear marker")
            //<DEBUG
        }
    }

    // return the instance that replaced us
    return otherCanvas;
},

//> @method canvas.setDrawContext() [A]
// Set the DOM location where this Canvas should draw.<P>
// If the Canvas has a Canvas parent, it will deparent().<P>
// If the Canvas is already drawn, it will draw in the new location.
//
// @param drawContext (DrawContext) DrawContext object indicating a DOM location.  Pass null
//                                  to clear the draw context and draw at top level.
// @group drawContext
// @visibility drawContext
//<
setDrawContext : function (drawContext) {
    var wasDrawn = this.isDrawn();

    // deparent if we have a parent, since we don't know if the drawContext is inside the
    // existing parent (we could check, but for now, having a drawContext is logically distinct
    // from having a Canvas parent)
    this.deparent();

    if (wasDrawn) this.clear();
    this.drawContext = drawContext;
    if (wasDrawn) this.draw();
},


// HTML for Canvas main tag
// --------------------------------------------------------------------------------------------

// _getDOMID - helper to provide unique IDs for our DOM elements
// These DOM IDs are arbitrary strings provided by ClassFactory.getDOMID() which we write into
// the appropriate DOM sub elements we create.
// dontCache parameter: If passed we don't maintain a map from the partName to the generated ID -
// any cacheing of the generated ID by partName should be handled by the calling code.
_getDOMID : function (partName, dontCache, dontReuse) {
    // Allow callers to handle their own cacheing if desired
    if (dontCache) {
        var ID = isc.ClassFactory.getDOMID(this.getID(), partName);
        if (this.reuseDOMIDs) {
            if (!this._uncachedDOMIDs) this._uncachedDOMIDs = [];
            this._uncachedDOMIDs[this._uncachedDOMIDs.length] = ID;
        }
        return ID;
    }

    if (!this._domIDs) this._domIDs = {};
    if (!this._domIDs[partName])
    this._domIDs[partName] = isc.ClassFactory.getDOMID(this.getID(), partName);

    return this._domIDs[partName];
},
// helper to retrieve the part name based on DOM ID
_getDOMPartName : function (domID) {
    if (!this._domIDs) return null;
    // This is a reverse lookup. If performance becomes a concern we could maintain
    // a reverse map instead...
    for (var ID in this._domIDs) {
        if (this._domIDs[ID] == domID) return ID;
    }
},
// reuseDOMIDs
// On destroy() we call ClassFactory.releaseDOMIDs() so the auto-generated DOM IDs may be reused
// within the page.
reuseDOMIDs:false,
_releaseDOMIDs : function () {
    if (!this.reuseDOMIDs) return;

    if (this._uncachedDOMIDs) {
        for (var i = 0; i < this._uncachedDOMIDs.length; i++) {
            isc.ClassFactory.releaseDOMID(this._uncachedDOMIDs[i]);
        }
    }

    if (this._domIDs) {
        //this.logWarn("to release:"+ this.echo(this._domIDs));
        for (var i in this._domIDs) {
            isc.ClassFactory.releaseDOMID(this._domIDs[i]);
        }
    }
},

//>	@method	canvas.getCanvasName()	(A)
//			return the name for this object in the DOM
//
//		@return	(string)	name of this canvas in the DOM
//
//<
_$canvas:"canvas",
getCanvasName : function () {
    // NOTE: this is called by getHandle(), imgHTML() and a few other spots, it needs to be
    // fast.
    
    if (!this._canvasName) this._canvasName = this._getDOMID(this._$canvas, true);
    return this._canvasName;
},

_$canvasClipDiv:"canvas_clipDiv",
_getClipDivDOMID : function () {
    return this._getDOMID(this._$canvasClipDiv);
},

getTransformCSS : function () {
    if (this.rotation != null) return ";" + isc.Element.getRotationCSS(this.rotation, this.transformOrigin);
    return null;
},

// If _useNativeTabIndex is true, is the focus handle actually the clipHandle
// overridden for GridRenderers in screenReader mode where we put native focus onto row elements
// within the grid body.
clipHandleIsFocusHandle:true,

//>	@method	canvas.getTagStart()	(A)
//			return the start tag for this canvas
//		@group	drawing
//
//		@return	(string)	start tag for this canvas
//<
getTagStart : function (dontConcat) {
    

    //this.logWarn("at draw, coordinates are: " + this.getRect());
    var canvas = isc.Canvas,
        handleOverflow = this._getHandleOverflow();

    

    // if we're set to automatic zIndex, resolve to a number now
    if (this.zIndex == canvas.AUTO) this.zIndex = canvas.getNextZIndex();

    // get the ID of the eventProxy for this object so we can write it into the tag
    var eventProxy = (this.eventProxy ? this.eventProxy.ID : this.ID);

    // for information on the necessity of this double DIV structure see "native size reporting
    // issues" comment
    var useClipDiv = this._shouldWriteClipDiv();
    var sizeArray = this._getInitialHandleSize(useClipDiv),
        width = sizeArray[0],
        height = sizeArray[1],
        left = this.left,
        top = this.top;

    if (this.showCustomScrollbars && this.vscrollOn && this.isRTL()) {
        left += this.getScrollbarSize();
    }

    // tabIndex, accessKey and focus.
    // ------------------
    // For accessability we need to support keyboard equivalents of everything you can
    // do with a mouse in ISC.
    // - tab and shift-tab are used to switch which ISC Canvas has the focus, such that
    //   it receives keyboard events.
    // - Alt+<accessKey> will 'jump' focus to the ISC canvas with the specified accessKey
    //   [In Firefox 2.0 this has become Alt+Shift+[accesskey] ]
    //
    // Where possible we should do this by leveraging native tabIndex and accessKey
    // support. This will minimize our strange interaction cases with elements that
    // support native tabIndex behavior (such as form items), and allow us to get
    // native, familiar tabIndex type focus behavior for free.
    // It also may be required for support of screen reader software.
    // We also need to support updating the tabIndex/accessKey of the widget on the fly.

    // Native tabIndex / accessKey behavior and considerations:
    // Not all browsers support focusability on every element type - some support focusability
    // only on form elements.  This creates a problem when we need to create keyboard
    // navigability for widgets whose rendering cannot possibly be based on the native <INPUT>
    // elements.
    //
    // ===== IE
    // Any element, including a DIV can be added to the tab-order of elements on a
    // page by setting it's TABINDEX property.  This can also be updated on the fly.
    // When native focus is given to an element, the onfocus handler is called for this
    // element (and when focus is taken away, onblur is fired for the element).
    // No document.onfocus / onblur event is fired when focusing on an HTML element.
    //
    // Setting the tabIndex property for any element in IE to be negative will exclude it
    // from the page's tab order.
    //
    // Setting the ACCESSKEY property on a div will cause focus to 'jump' to that div when
    // alt+accessKey is pressed.  Note that the accessKey can be set to any alpha-numeric
    // value (no symbols or full strings, case-insensitive for letters).
    //
    // Implementation for IE:
    //  -- Add TabIndex to the handle
    //  -- Add onfocus / onblur to keep track of which ISC element has focus
    //  -- Have widget.focus() and widget.blur() to call native element.focus() and
    //     element.blur()
    //
    // ===== Mozilla
    // - Firefox 1.5 and above: identical to IE, except for the following additional
    //   workarounds
    //   Limitations:
    //      AccessKeys - in Moz FF 1.5, an accessKey set on a div is not respected.
    //      We workaround this by writing an empty "<a>" tag between the clip div and the
    //      content div (after the content div), with a specified accessKey and a focus handler
    //      that puts focus into the widget.
    //      See _makeAccessKeyProxy().
    //      Focus on mouse down - In moz a div with a tabIndex will receive focus when clicked.
    //      However we set the tabIndex on the clipDiv, not the content div (this is appropriate
    //      - if we set the tabIndex on the content div, the focus outline appears around the
    //      text rather than around the entire widget). In this case clicking on the content
    //      div fails to put focus onto the clip div. We workaround this by explicitly focusing
    //      in the widget on mousedown in EventHandler.js
    //
    // Previous to FF 1.5, there seems to be no support for TABINDEX on any elements other than
    // "interactive" HTML elements.  This includes form items, buttons and
    // <a> </a> or <area> </area> tags *with an href set*.
    // Note: <area> tags are used within the <map> tag of a client side image-map to
    // denote clickable areas.
    // Therefore we can't use the same approach as in IE or just writing a tabIndex
    // directly onto a widget's handle.
    // Possible approaches:
    //  - We could set an <a> tag around the handle with an onclick / onmouseDown handler
    //    that prevents the "href" from ever being activated.
    //    Has some negative possible side effects,
    //      - window.status gets updated when the <a> gets focus
    //      - SPACE may attempt to navigate the browser to the href specified
    //      - changes the style of text within the handle
    //  - We can draw an interactive tag offscreen for each focusable item, where:
    //      - onfocus / onblur handler for the offscreen tag would update the ISC focus.
    //      - interactive tag's tabIndex and accessKey matches the specified values for the
    //        widget
    //    This is the approach we've taken - we're drawing offscreen 'focusProxy' button
    //    elements, to hold the widget's place in the page's native tab order.
    //
    // Note - to exclude focusable items from the page's tab order in Moz we must make
    // use of the property '-moz-user-focus' - setting this to "ignore" will exclude the
    // item from the page's tab order.  In order to make this more user-friendly,
    // automatically interpret a negative specified tabIndex to mean the developer wants the
    // widget excluded from the tab order, and set this property to 'ignore' on the
    // focusProxy.
    //
    // Implementation, for Moz pre ff 1.5:
    //  -- For each 'focusable' widget, create a button element called a focusProxy.
    //     This button is hidden - it is clipped by a parent div, and absolutely positioned
    //     behind the widget on the page (to ensure that when the element receives native focus
    //     it is scrolled into view.
    //  -- Give the focusProxy the same tabIndex and accessKey as the widget.
    //  -- Write onfocus and onblur handlers for the focusProxy that put the 'virtual ISC
    //     focus' onto the widget
    //  -- Modify the 'setFocus()' method to natively focus on the focusProxy for a widget.
    //     (this in turn fires the onfocus handler that tells the EventHandler which widget
    //     has ISC-focus)
    //  -- Ensure the focusProxy stays 'in synch' with the widget's handle -- this means
    //      o update the visibility of the focusProxy as the visibility of the handle
    //        is changed - this correctly excludes hidden widgets from the page's tab order.
    //      o clear the focusProxy when the widget get's cleared (and write it back out if
    //        the widget is draw()n)
    //      o clear / redraw the focusProxy if the 'setCanFocus()' method updates a widget's
    //        focus-ability at runtime.
    //
    // ===== Chrome
    // In Chrome, we need to leverage the accessKeyProxy subsystem - unfortunately, onfocus()
    // doesn't always fire in Chrome (not at all for some form-item types and in some other
    // specific circumstances) - however, in such circumstances, it *will* still fire
    // onClick().  So, specifically for Chrome, handle onClick() rather than onfocus()in the
    // adjacentHTML we write out for the proxy. *Note*: as at now (8/3/12), certain
    // key-combinations that are implemented internally by Chrome seem not to be useable by SC.
    // For instance, Shift-Alt-B always selects the Bookmarks bar in Chrome (and with no
    // visible indication that it's done so until you use the arrow-keys to move the cursor,
    // giving the appearance that focus has simply disappeared!)
    //
    // =====
    // In Safari native buttons and native div's dont support keyboard access via tabbing.
    // FormItems do, so we use a TextArea as our focusProxy in this browser. Other than this
    // the logic is the same as for Moz pre FF 1.5
    //
    // Suppressing native focus outlines:
    // Native focus outlines show up in IE and Moz 1.5 and above (where we use native tab indices)
    // We want to suppress these for some widgets, such as menus.
    // This is controlled by the showFocusOutline property (defaults to true, set to false to suppress the
    // native focus outline).
    // In IE we use the proprietary 'hideFocus=true' attribute to suppress this
    // In Moz we use the css attribute '-moz-outline-style:none'

    if (!canvas._onFocus) {
        canvas._onFocus = " onfocus=";
        canvas._onBlur = " onblur=";
        canvas._tabIndex = " tabindex="
        canvas._accessKey = " accessKey=";
    }

    var isMoz = isc.Browser.isMoz;

    // Convert this.opacity to a usable value
    var opacity = this.opacity;
    // CSS opacity uses a decimal between 0 and 1 approach for specifying opacity (correct for
    // both Moz, Safari, and IE9)
    if (!isc.Browser.isIE || isc.Browser.isIE9) {
        if (opacity != null) opacity = opacity / 100;
    }
    if (isc.Browser.isMoz) {
        
        if (this.smoothFade && (opacity == 1 || opacity == null)) opacity = 0.9999;
    }

    if (useClipDiv) {
        //>DoubleDiv

        var cursor = this.getCurrentCursor(),
            focusString,
            focusOutlineStyle,
            nativeTabIndex = this._useNativeTabIndex;

        if (this.clipHandleIsFocusHandle == false) nativeTabIndex = false;

        if (nativeTabIndex && this._canFocus()) {
            focusString = isc.SB.concat(
                canvas._onFocus, this._getNativeFocusHandlerString(),
                canvas._onBlur, this._getNativeBlurHandlerString(),
                !this.isDisabled() ? canvas._tabIndex + this.getTabIndex() : null,
                // Don't write an accessKey into the handle if we're going to use
                // an accessKeyProxy
                (!this._useAccessKeyProxy() && this.accessKey != null) ?
                    canvas._accessKey + this.accessKey :
                    null
            );

            if (isc.Browser.isMoz) {
                // In Moz, if we are using native tab-indices, the dotted focus
                // outline by default appears 1px outside the clip-div.
                // This would be fine except it is clipped by any parents of this widget and
                // obscured by any other siblings that are adjacent to the widget and have a
                // higher z-index.
                // Moz has a useful css extension -moz-outline-offset which allows us to
                // have the focus outline render a specified distance from the element, including
                // inside, via applying a negative value. This avoids the problem with parents
                // and siblings obscuring the outline.
                // In most cases we use this and it gives us a reasonable solution for a focus
                // outline.
                // However the contents of the content-div  div will obscure the
                // outline if it contains some solid element that extends to the edge of the
                // canvas, such as a  grid-renderer's table, or an image.
                // We could possibly resolve this issue by using the "focus" pseudo css class
                // to apply a custom border in this case instead of relying on the native focus
                // outline.  However this could shift internal content, and besides, we expect
                // this bug will be fixed.
                // For now just provide a property so we can set the offset depending on the
                // widget in question to give as much flexibility as possible.
                // Note: If the widget has a border, or padding, the focus outline shows up
                //  over the border / padding rather than over the content, when the specified
                //  offset is -1px.
                // Note: there is a further Moz bug with widgets showing native scrollbars where,
                // when scrolling, a series of horizontal lines appears over the content due to
                // incorrect redraw of the focus outline.
                focusOutlineStyle = isc.StringBuffer.concat(
                    (this.mozOutlineOffset != null
                        ? ";-moz-outline-offset:" + this.mozOutlineOffset : null),
                    // We also allow customization of the color of the Moz Focus outline.
                    // By default, in FF 1.5 (tested against 1.5.0.3) the color is supposed to invert
                    // whatever it's sitting on top of - however when the background is gray
                    // the focus outline is the same color so isn't visible.
                    // We therefore allow per-widget customization of the color.
                    (this.mozOutlineColor != null
                        ? ";-moz-outline-color:" + this.mozOutlineColor : null),
                    
                    (!this.showFocusOutline
                        ? ";-moz-outline-style:none" : null)
                );
            // Safari also supports suppressing focus outline via simple "outline" css
            
            } else if (isc.Browser.isSafari) {
                if (!this.showFocusOutline) {
                    focusOutlineStyle = ";outline-style:none";
                }
            }
        }

        // use two DIVs: an inner one to hold the content, and an outer one for clipping
        var output = isc.StringBuffer.concat(

            // the clipDiv
            "<div id='" , this._getClipDivDOMID(),
            "' eventProxy=" , eventProxy,
            (this.ariaRole ? " role='" + this.ariaRole + "'" : ""),
            (this.ariaState && this.getAriaStateAttributes ? this.getAriaStateAttributes() : ""),
            (this.className ? " class='" + this.className + "'" : ""),
            focusString,
            " style='",
                "POSITION:" , this.position,
                ";LEFT:" , left,
                "px;TOP:" , top,
                "px;WIDTH:" , width,
                "px;HEIGHT:" , height,
                "px;Z-INDEX:" , this.zIndex,
                (this.visibility == canvas.INHERIT ? "" : ";VISIBILITY:" + this.visibility),
                (this.backgroundColor == null ?
                    "" : ";BACKGROUND-COLOR:" + this.backgroundColor),
                (this.backgroundImage == null ? "" :
                    ";BACKGROUND-IMAGE:url(" + this.getImgURL(this.backgroundImage) +")" +
                    ";BACKGROUND-REPEAT:"+this.backgroundRepeat +
                    (this.backgroundPosition ?
                        ";BACKGROUND-POSITION:"+this.backgroundPosition : "")),
                (this.textColor == null ? "" : ";COLOR:" + this.textColor),

                // border on outer DIV because it should not scroll
                (this.border ? ";BORDER:" + this.border : ""),
                (this.borderRadius ? ";BORDER-RADIUS:" + this.borderRadius : ""),

                // padding should scroll and should be included in the drawn content size,
                // so it goes on the contentDiv.
                // If this.padding is set, force the padding for the clipDiv to be zero.
                // Therefore if the style applied to the clipDiv has padding specified we
                // don't draw something with both sets of padding.
                
                (this._willSuppressOuterDivPadding() ? ";PADDING:0px" : ""),

                // margin must also be on the outer container, since borders are
                this._getMarginHTML(),
                // In Moz we set style.-moz-opacity to a value between zero and one to get opacity
                // In Safari we set style.opacity to a value between zero and one.
                (opacity != null ?
                    (this._useMozOpacity ? ";-moz-opacity:" : ";opacity:") + opacity :
                    ""),
                // use box sizing model where specified size includes border and padding
                (!this.isBorderBox ? null
                                   : (isMoz ? ";-moz-box-sizing:border-box"
                                            : ";-webkit-box-sizing:border-box;box-sizing:border-box")),
                focusOutlineStyle,
                this.getTransformCSS(),
                // Touch browsers: set -webkit-user-select:none to disable user selection
                // for copy (touch and hold/wait for blue rectangle).
                (isc.Browser.isTouch
                    ? (!this.canSelectText ? ";-webkit-user-select:none" : ";-webkit-user-select:text")
                    : null),

                ";OVERFLOW:",
                handleOverflow,
                
                ";' ONSCROLL='return " + eventProxy + "._handleCSSScroll()'>",

            // the contentDiv
            "<div id='" , this.getCanvasName(),
            "' eventProxy='" , eventProxy,
            (this.textDirection != null ? "' dir='" + this.textDirection : ""),
            "' style='POSITION:relative;",
                
                (!(this.padding > 0 || (this.topPadding > 0 && this.bottomPadding > 0))
                 ? (isc.Browser.isSafari
                    ? "-webkit-margin-collapse:separate separate;"
                    : (!(isc.Browser.isOpera && isc.Browser.version < 10)
                       ? "display:inline-block;-moz-box-sizing:border-box;box-sizing:border-box;width:100%;vertical-align:top;"
                       : ""))
                 : ""),
                "VISIBILITY:inherit;Z-INDEX:" , this.zIndex,
                (cursor == canvas.AUTO ? "" : ";CURSOR:" + cursor),
                // padding should be included in the drawn content size, so it goes on the
                // contentDiv
                (this.padding != null ? ";PADDING:" + this.padding + "px" : ""),
                // Unexposed per-side padding
                (this.topPadding != null ? ";padding-top:" + this.topPadding + "px" : ""),
                (this.bottomPadding != null ? ";padding-bottom:" + this.bottomPadding + "px" : ""),
                (this.leftPadding != null ? ";padding-left:" + this.leftPadding + "px" : ""),
                (this.rightPadding != null ? ";padding-right:" + this.rightPadding + "px" : ""),
                ";'>"
        );

        //<DoubleDiv
    } else { // Use a single DIV
        //>SingleDiv
        var styleEndSlot = 61;
        if (!canvas._divHTML) {
            canvas._absolutePos = " style='POSITION:absolute;LEFT:";
            canvas._relativePos = " style='POSITION:relative;LEFT:";
            canvas._className = " class='";
            canvas._closeClassName = "'";
            canvas._visibility = ";VISIBILITY:";
            canvas._$cursor = ";CURSOR:";

            var divHTML = canvas._divHTML = [];
            divHTML[0] = "<div id=";
            // [1] ID
            divHTML[2] = " eventProxy=";
            // [3] eventProxy
            // [4] optional " CLASS=";
            // [5] optional className
            // [6] optional close className
            // [7] optional textDirection
            // [8] " STYLE='POSITION:" relative or absolute + ";LEFT:"
            // [9-14] left
            divHTML[15] = "px;TOP:";
            // [16-21] top
            divHTML[22] = "px;WIDTH:";
            // [23-27] width
            divHTML[28] = "px;HEIGHT:";
            // [29-33] height

            divHTML[34] = "px;Z-INDEX:";
            // [35-41] zIndex

            divHTML[44] = ";OVERFLOW:";
            // [45] overflow
            // [46] visibility
            // [47] visibility
            // [48] background-color
            // [49] background-image
            // [50] Moz box sizing
            // [51] cursor
            // [52] cursor
            // [53] margin
            // [54] padding
            // [55] border
            // [56] opacity
            // [57] flash filter
            // [58] CSS transforms
            // [59] margin-collapse
            // [60] user-select
            // NOTE: in IE, DIV scroll events can't be captured at the window level.
            divHTML[styleEndSlot] = "' ONSCROLL='return ";
            // [styleEndSlot + 1] eventProxy
            divHTML[styleEndSlot + 2] = "._handleCSSScroll()' ";
            // [gap]
            // [several slots (styleEndSlot + 4)+] focus/blur/tabIndex/accessKey
        }
        var divHTML = canvas._divHTML;

        divHTML[1] = this.getCanvasName();
        divHTML[3] = eventProxy;
        // optional className (note that Button and other table-based Canvii omit className at
        // the DIV level and apply it to the cell)
        if (this.className != null) {
            divHTML[4] = canvas._className;
            divHTML[5] = this.className;
            divHTML[6] = canvas._closeClassName;
        } else {
            divHTML[4] = divHTML[5] = divHTML[6] = null;
        }

        divHTML[7] = (this.textDirection != null ? " dir=" + this.textDirection : null);

        divHTML[8] = (this.position == canvas.RELATIVE ? canvas._relativePos :
                      canvas._absolutePos);

        // 6 slots mostly due to the tendency to use "-10000" as a large offscreen coordinate
        isc._fillNumber(divHTML, left, 9, 6);
        isc._fillNumber(divHTML, top, 16, 6);
        isc._fillNumber(divHTML, width, 23, 5);
        isc._fillNumber(divHTML, height, 29, 5);

        
        if (this.zIndex != canvas.AUTO) isc._fillNumber(divHTML, this.zIndex, 35, 9);
        else {
            divHTML[35] = this.zIndex;
            divHTML[36] = divHTML[37] = divHTML[38] = divHTML[39] =
                    divHTML[40] = divHTML[41] = divHTML[42] = divHTML[43] = null;
        }

        divHTML[45] = handleOverflow;
        if (this.visibility != canvas.INHERIT) {
            divHTML[46] = canvas._visibility;
            divHTML[47] = this.visibility;
        } else {
            divHTML[46] = divHTML[47] = null;
        }
        divHTML[48] = (this.backgroundColor == null ? null :
                       ";BACKGROUND-COLOR:" + this.backgroundColor);
        divHTML[49] = (this.backgroundImage == null ? null :
                       ";BACKGROUND-IMAGE:url(" + this.getImgURL(this.backgroundImage) +
                       ");BACKGROUND-REPEAT:"+this.backgroundRepeat +
                    (this.backgroundPosition ?
                        ";BACKGROUND-POSITION:"+this.backgroundPosition : ""));
        if (!this.isBorderBox) {
            divHTML[50] = null;
        } else {
            
            if (isMoz) {
                divHTML[50] = ";-moz-box-sizing:border-box";
            } else if (isc.Browser.isWebKit) {
                divHTML[50] = ";-webkit-box-sizing:border-box;box-sizing:border-box";
            } else {
                divHTML[50] = ";box-sizing:border-box";
            }
        }
        
        var cursor = this.getCurrentCursor();
        if (cursor == canvas.AUTO) {
            divHTML[51] = divHTML[52] = null;
        } else {
            divHTML[51] = canvas._$cursor;
            divHTML[52] = cursor;
        }
        divHTML[53] = this._getMarginHTML();
        divHTML[54] = (this.padding != null ? ";PADDING:" + this.padding + isc.px : null);
        // Unexposed per-side padding
        if (this.topPadding != null)
            divHTML[54] = (divHTML[54] || "") + ";padding-top:" + this.topPadding + "px";
        if (this.bottomPadding != null)
            divHTML[54] = (divHTML[54] || "") + ";padding-bottom:" + this.bottomPadding + "px";
        if (this.leftPadding != null)
            divHTML[54] = (divHTML[54] || "") + ";padding-left:" + this.leftPadding + "px";
        if (this.rightPadding != null)
            divHTML[54] = (divHTML[54] || "") + ";padding-right:" + this.rightPadding + "px";

        var borderHTML = this._getBorderHTML() || "",
            borderRadiusHTML = this._getBorderRadiusHTML() || "";
        divHTML[55] =  borderHTML + borderRadiusHTML;
        if (isc.Browser.isIE && !isc.Browser.isIE9) {

            if (!isc.Browser.useCSSFilters && isc.Img && isc.isA.Img(this)) {
                divHTML[56] = ";filter:none;";
                divHTML[57] = null;
            } else {
                 if (!isc.Canvas.neverUseFilters || this.useOpacityFilter) {
                     divHTML[56] = (opacity == null ? null :
                           ";filter:progid:DXImageTransform.Microsoft.Alpha(opacity="+opacity+")");
                } else {
                	divHTML[56] = null;
    			}
                if (!isc.Canvas.neverUseFilters) {
                    
                    if (this._avoidRedrawFlash) {
                        divHTML[57] = ";filter:progid:DXImageTransform.Microsoft.iris(irisStyle=circle)";
                    } else {
                        divHTML[57] = null;
                    }
                } else {
                	divHTML[57] = null;
                }
            }

        } else {
            if (opacity != null) {
                divHTML[56] = (this._useMozOpacity ? ";-moz-opacity:" : ";opacity:") + opacity;
            } else {
                divHTML[56] = null;
            }
        }
        divHTML[58] = this.getTransformCSS();

        if (isc.Browser.isWebKit) {
            divHTML[59] = ";-webkit-margin-collapse:collapse collapse";
        } else if (isc.Browser._useNewSingleDivSizing) {
            divHTML[59] = ";display:inline-block";
        } else {
            divHTML[59] = null;
        }

        if (isc.Browser.isTouch) {
            divHTML[60] = !this.canSelectText ? ";-webkit-user-select:none" : ";-webkit-user-select:text";
        } else {
            divHTML[60] = null;
        }

        divHTML[styleEndSlot + 1] = eventProxy;

        var lastSlot = styleEndSlot + 5;
        if (this._canFocus() && this._useNativeTabIndex && this.clipHandleIsFocusHandle) {
            divHTML[styleEndSlot + 5] = canvas._onFocus;
            divHTML[styleEndSlot + 6] = this._getNativeFocusHandlerString();
            divHTML[styleEndSlot + 7] = canvas._onBlur;
            divHTML[styleEndSlot + 8] = this._getNativeBlurHandlerString();
            if (!this.isDisabled()) {
                divHTML[styleEndSlot + 9] = canvas._tabIndex;
                isc._fillNumber(divHTML, this.getTabIndex(), styleEndSlot + 10, 5, true);
                if (this.accessKey != null) {
                    divHTML[styleEndSlot + 15] = canvas._accessKey;
                    divHTML[styleEndSlot + 16] = this.accessKey;
                    lastSlot = styleEndSlot + 17;
                } else lastSlot = styleEndSlot + 15;

                if (!this.showFocusOutline) {
                    if (!canvas._$hideFocus) canvas._$hideFocus = " hideFocus=true";
                    divHTML[lastSlot] = canvas._$hideFocus;
                    lastSlot += 1;
                }

            } else lastSlot = styleEndSlot + 9;
        }

        if ((this.ariaRole || this.ariaState) &&
            isc.Canvas.ariaEnabled() && !isc.Canvas.useLiteAria())
        {
            if (this.ariaRole) {
                divHTML[lastSlot++] = " role='";
                divHTML[lastSlot++] = this.ariaRole;
                divHTML[lastSlot++] = "' ";
            }
            if (this.ariaState && this.getAriaStateAttributes) {
                divHTML[lastSlot++] = this.getAriaStateAttributes();
            }

        }

        

        // trim focus-related strings left in the template by the last widget, and end
        // start tag
        divHTML.length = lastSlot;
        divHTML[lastSlot] = this._$rightAngle;

        // code to grab a sample of the HTML written for the first instance of each class
        /*
        var className = this.getClass();
        if (!canvas._sampled) canvas._sampled = {};
        if (!canvas._sampled[className]) {
            this.logWarn("html for first instance of:" + className + ": " + divHTML.join(""));
            canvas._sampled[className] = true;
        }
        */

        if (dontConcat) output = divHTML;
        else output = divHTML.join(isc.emptyString);
        //<SingleDiv
    }

    this._drewClipDiv = useClipDiv;

    return output;
},



_$marginLeft : "MARGIN-LEFT:",
_$marginRight : "MARGIN-RIGHT:",
_$marginTop : "MARGIN-TOP:",
_$marginBottom : "MARGIN-BOTTOM:",
_$margin : "MARGIN:",

_getMarginHTML : function () {

    // optimization: if we have nothing that would introduce automatic per-side margin
    // settings..
    if (!this._edgesAsPeer() && this._attachedPeerMap == null) {
        // don't write out margins CSS if we have no margins setting
        if (this.margin == null) return null;
        // write out only a symmetric margin setting
        return isc.SB.concat(isc.semi, this._$margin, this.margin, isc.px);
    }

    // Support assymetric margins if necessary.
    var margins = this._calculateMargins(),
        cssText = isc.SB.concat(
             isc.semi, this._$marginLeft, margins.left, isc.px,
             isc.semi, this._$marginRight, margins.right, isc.px,
             isc.semi, this._$marginTop, margins.top, isc.px,
             isc.semi, this._$marginBottom, margins.bottom, isc.px
        );
    //this.logWarn("margins: " + cssText);
    return cssText;
},

_getBorderHTML : function () {
    return this.border ? ";BORDER:" + this.border : null;
},

_getBorderRadiusHTML : function () {
    return this.borderRadius ? ";BORDER-RADIUS:" + this.borderRadius : null;
},

//>	@method	canvas.getTagEnd()	(A)
//			return the end tag for this canvas
//		@group	drawing
//
//		@return	(string)	end tag for this canvas
//<
_singleDIV : "</div>",
_doubleDIV : "</div></div>",
getTagEnd : function () {

    //>DoubleDiv two closing tags if clipDiv is being used.
    if (this._drewClipDiv) return this._doubleDIV;
    //<DoubleDiv

    //>SingleDiv
    return this._singleDIV;
    //<SingleDiv
},

// _getHandleOverflow()
// Internal method to determine the desired overflow setting for the widgets handle.
// Note - this is used for the clipHandle (not the contentHandle) if we're showing a clipHandle.
// Called from both getTagStart() and setOverflow()
_getHandleOverflow : function () {
    // if we're writing an IFrame into our handle, we size it to fit our
    // content. Always suppress any native scrollbars on our handle. If scrolling of the
    // iframe content is required, this is handled by native scrollbars on the iframe 
    // handle.
    
    if (this.containsIFrame()) {
        return this._$hidden;
    }

    var overflow = this.overflow;
    var scrolling = (this.overflow == isc.Canvas.SCROLL || this.overflow == isc.Canvas.AUTO),
        customScrolling = scrolling && this.showCustomScrollbars,
        nativeScrolling = scrolling && !this.showCustomScrollbars;
    // when we use custom scrollbars, we tell the browser that the overflow should be hidden so
    // that the browser doesn't draw scrollbars.  Then we have our scrollbars move the hidden
    // overflow into view via scripting.
    // See getScrollingMechanism(), and comments near showCustomScrollbars definition above.
    if (this.overflow == isc.Canvas.HIDDEN || customScrolling)
    {
        
        if (this._useMozScrollbarsNone) {
            overflow = this._canScrollHidden ? this._$hidden : "-moz-scrollbars-none";
            this._useMozScrollSize = true;
        } else {
            overflow = this._$hidden;
        }

    } else if (isc.Browser.isOpera && this.overflow == isc.Canvas.VISIBLE) {
        overflow = this._$hidden;
    } else if (isc.Browser.isIE    && this.overflow == isc.Canvas.VISIBLE) {
        if (this.forceHandleOverflowHidden) overflow = this._$hidden;
    } else if (isc.Browser.isMoz) {
        if (nativeScrolling) this._useMozScrollSize = true;
        
        else if (this._useMozScrollbarsNone) {
            overflow = this._canScrollHidden ? this._$hidden : "-moz-scrollbars-none";
            this._useMozScrollSize = true;
        }
    }

    // if a clipDiv is being used, it will need to be set to overflow:hidden if the canvas is to
    // behave as overflow:clip-h/clip-v, because an overflow:visible DIV won't clip contained
    // content, and we can't use clip regions in NS6.
    if (this._drewClipDiv &&
        (this.overflow == isc.Canvas.CLIP_H || this.overflow == isc.Canvas.CLIP_V))
    {
        overflow = this._$hidden;
    }
    return overflow;
},

// _getInitialHandleSize()
// Internal function to return the specified width / height to write out to the handle initially
// used by getTagStart() and setOverflow()
// NOTE: returns the same Array instance every time.  Retrieve values before calling again.
_fillArray : [],
_getInitialHandleSize : function (useClipDiv) {
    
    var width = this.getInitialWidth(),
        height = this.getInitialHeight();
    return this._adjustHandleSize(width, height, useClipDiv);
},

// NOTE: these two very advanced functions allow you to specify an initial size for the handle
// which differs from this.width/height.  To be really complete, allowing the handle to have a
// completely different size from the specified size of the Canvas, similar entry points should
// exist for setHandleRect().
getInitialWidth : function () {
    return this.getWidth();
},

getInitialHeight : function () {
    return this.getHeight();
},

// In Mozilla we explicitly specify border-box sizing for canvii.
// In other browsers we have no control over border-box vs content-box sizing, so we
// rely on the default browser behavior for DIVs
isBorderBox:(isc.Browser.isMoz || isc.Browser.isBorderBox),


//> @method canvas._adjustHandleSize()  (I)
//      Internal helper method.
//      Given a desired width and height, return the width and height we must actually write into
//      the handle such that when drawn, the widget, will be the specified size, including all
//      border and padding (effectively fitting the 'content box' sizing model.)
//      Also adjusts for space taken up by custom scrollbars if required.
//      Called from getTagStart() and setHandleRect();
//
//      NOTE: returns the same Array instance every time.  Don't call again without retrieving
//      values first.
//
//      @visibility internal
//      @group  sizing
//
//      @return     (array)     2 element array containing [width,height] to write into the handle
//<
_adjustHandleSize : function (width, height, useClipDiv) {

    
    var margins = this._calculateMargins();

    if (useClipDiv == null) useClipDiv = this._drewClipDiv;

    // If passed null for width / height just return it
    if (width != null) {
        // when using custom scrollbars, we shrink the handle to leave room for the scrollbar
        if (this.showCustomScrollbars && this.vscrollOn) {
            width -= this.getScrollbarSize();
            
        }

        // The CSS2 box model specifies that content can be surrounded, from inside to out, by
        // padding, then a border, then margins.
        //     http://www.w3.org/TR/REC-CSS2/box.html
        //
        // The CSS2 spec also says that the width and height specified for an element should be
        // taken as the width and height *without* padding, border or margins (the "content-box"
        // model).  Older browsers implemented width/height as meaning width/height *with*
        // border and padding (the "border-box" model).
        //
        // In CSS3, you will be able to specify what sizing model you want via the "box-sizing:"
        // attribute.  Some browsers already support this.
        //
        // The border-box model is much saner, as the alternative is that in order to get
        // something to be a particular size in order to fit into a layout, you have to subtract
        // off the size of the border and padding up front, which implies you must know the
        // border and padding size despite the fact that such things are supposed to be
        // externalized into style sheets.
        //
        // We take this a step further - the specified size for any widget will be the drawn size
        // including border, padding, AND margins.
        // This is desirable because it allows the developer to handle separating widgets within
        // layouts by applying margins to the widgets.
        // We handle this by subtracting the margins (and in browsers that cannot be told to use
        // the border-box model, padding and border) from a widget, and applying that adjusted
        // size to the handle.
        //
        //

        // In order to get the desired handle size (to write into the DOM):
        // In all browsers subtract margin sizes.
        //
        width -= (margins.left + margins.right);
        //width -= (this.getLeftMargin() + this.getRightMargin());

        if (this.isBorderBox) {
            // border-box (either DIV structure): nothing
        } else if (useClipDiv) {
            // double DIV structure used in browsers with bad size reporting.
            // If padding is explicitly specified, it gets placed on the content div.
            // If padding is specified in the css style definition, it's typically placed
            // on the outer div, so we need to adjust for border and padding.
            // Exception: If the _suppressOuterDivPadding property has been set we
            // explicitly zero out padding on the clip div and apply it to the content
            if (!this._willSuppressOuterDivPadding(false, true)) {
                width -= this.getHBorderPad();
            } else {
                width -= this.getHBorderSize();
            }

        } else {
            // single DIV content-box, eg, IE strict
            width -= this.getHBorderPad();
        }
    }

    if (height != null) {

        if (this.showCustomScrollbars && this.hscrollOn) {
            height -= this.getScrollbarSize();
        }
        height -= (margins.top + margins.bottom);

        if (this.isBorderBox) {
        } else if (useClipDiv) {
            if (!this._willSuppressOuterDivPadding(true, false)) {
                height -= this.getVBorderPad();
            } else {
                height -= this.getVBorderSize();
            }
        } else {
            height -= this.getVBorderPad();
        }
    }

    // If the sizes are negative default them to 1
    
    if (width != null && width < 1) {
        this.logInfo("Specified width:" + this.getInitialWidth() + " adjusted for border, margin, " +
                     "and scrollbars would cause initial handle size to be less than or equal to " +
                     "zero, which is not supported. Clamping handle width to 1px.", "sizing");
        width =1;
    }
    if (height != null && height < 1) {
        this.logInfo("Specified height:" + this.getInitialHeight() + " adjusted for border, margin, " +
                     "and scrollbars would cause initial handle size to be less than or equal to " +
                     "zero, which is not supported. Clamping handle height to 1px.", "sizing");
        height =1;
    }

    // NOTE: reuse an Array
    var arr = this._fillArray;
    arr[0] = width;
    arr[1] = height;
    return arr;
},

// For double-div widgets, padding should scroll and should be included in the
// drawn content size, so it goes on the contentDiv.
// If this.padding is set, force the padding for the clipDiv to be zero.
// Therefore if the style applied to the clipDiv has padding specified we
// don't draw something with both sets of padding.

_willSuppressOuterDivPadding : function (v, h) {
    return (this.padding != null || this._suppressOuterDivPadding ||
            // vertical param explicitly false - we don't care about
            // top/bottom padding - otherwise check for it.
            (v != false ? (this.topPadding != null || this.bottomPadding != null) : false) ||
            (h != false ? (this.leftPadding != null || this.rightPadding != null) : false) );
},


// _getNativeFocusHandlerString() and _getNativeBlurHandlerString()
// These methods return the native onblur / onfocus handler function strings for use when
// using native focus / blur behavior.  Used by getTagStart / getFocusProxyHTML when writing
// out the onblur / onfocus handler attributes.
_$focusStart : "isc.EH.focusInCanvas(",

_$mozFocusStart : "if(event.target!=this)return;isc.EH.focusInCanvas(",

_$blurStart : "if(window.isc)isc.EH.blurFocusCanvas(",
_$focusEnd : ",true);",
_getNativeFocusHandlerString : function (unquoted) {
    var ID = this.getID();
    var quote = unquoted ? null : this._$singleQuote;
    if (isc.Browser.isMoz)
            return isc.SB.concat(quote, this._$mozFocusStart, ID, this._$focusEnd, quote);
    return isc.SB.concat(quote, this._$focusStart, this.getID(), this._$focusEnd, quote);
},

_getNativeBlurHandlerString : function (unquoted) {
    var quote = unquoted ? null : this._$singleQuote;
    return isc.SB.concat(quote, this._$blurStart, this.getID(), this._$focusEnd, quote);
},

// _getNativeFocusHandlerMethod / _getNativeBlurHandlerMethod
// Returns the native focus / blur handlers as a method constructed from the native focus/blur
// handler strings.
// This can then be assigned directly to the handle's onfocus / onblur attribute after
// the handle has been written out.
_getNativeFocusHandlerMethod : function () {
    if (!this._nativeFocusHandlerMethod) {
        this._nativeFocusHandlerMethod = new Function ("event", this._getNativeFocusHandlerString(true));
    }
    return this._nativeFocusHandlerMethod;
},
_getNativeBlurHandlerMethod : function () {
    if (!this._nativeBlurHandlerMethod) {
        this._nativeBlurHandlerMethod = new Function ("event", this._getNativeBlurHandlerString(true));
    }
    return this._nativeBlurHandlerMethod;
},
// Handles: pointers to the Canvas' DOM representation
// --------------------------------------------------------------------------------------------

_getAriaHandleID : function () {
    var isDrawn = this.isDrawn();
    if ((isDrawn && this._drewClipDiv) ||
        (!isDrawn && this._shouldWriteClipDiv()))
    {
        return this._getClipDivDOMID();
    } else {
        return this.getCanvasName();
    }
},

//> @method canvas.getHandle()  (A)
//          get the handle to this layer
//      @group  handles
//
//		@return	(handle)	handle to this layer
//<
getHandle : function () {
    if (isc._traceMarkers) arguments.__this = this;

    
    if (this.destroyed) {
        this.logWarn("Attempt to access destroyed widget in the DOM - " +
            "destroy() called at invalid time (eg: mid-draw) or invalid method " +
            "called on destroy()d widget. Stack Trace:" + this.getStackTrace());
        
    }

    // don't look for the handle unless we're drawn
    
    //if (!(this._handleDrawn || this._drawn) || this._clearedByParent) return null;
    if (!(this._handleDrawn || this._drawn)) return null;

    // if the handle is not already defined, find it
    if (this._handle == null) {
        // get the ID we wrote into the DOM for the handle
        var elementId = this.getCanvasName();
        // and get the handle by id
        this._handle = this.ns.Element.get(elementId);

        // if we can't find the handle, since we're supposedly drawn, this is an error
        if (this._handle == null) {
            this.logWarn("Unable to find handle for drawn Canvas, elementId: " + elementId);
        }
    }
    return this._handle;
},

//> @method canvas.getClipHandle() (A)
//
//      @group  handles
//      @return (handle)  clipDiv handle to this layer
//<
getClipHandle : function () {
    // if we're not using a separate clip vs content DIV, the clip handle is just the handle
    if (!this._drewClipDiv) return this.getHandle();

    //>DoubleDiv
    // don't look for the handle unless we're drawn
    //if (!(this._handleDrawn || this._drawn) || this._clearedByParent) return null;
    if (!(this._handleDrawn || this._drawn)) return null;


    // if the handle is not already defined, find it
    if (this._clipDiv == null) {
        // get the document the handle would be in
        var elementId = this._getClipDivDOMID();

        // and get the handle by id
        this._clipDiv = this.ns.Element.get(elementId);

        // if we can't find the handle, since we're supposedly drawn, this is an error
        if (this._clipDiv == null) {
            this.logWarn("Unable to find clipHandle for drawn Canvas, elementId: " + elementId);
        }
    }
    return this._clipDiv;
    //<DoubleDiv
},

//> @method canvas.getOuterElement() [A]
// Returns the outer DOM element of this Canvas. This method is provided for the 
// very rare cases where a programmer needs to examine the DOM hierarchy created by
// a drawn SmartClient component.
// <P>
// Direct manipulation of the DOM elements created by SmartClient components
// is not supported. SmartClient components should be rendered or cleared using
// standard methods such as +link{canvas.draw()}, +link{canvas.clear()}. If direct
// integration with existing DOM structures is required, this should be achieved via the
// +link{canvas.htmlElement} attribute, rather than by attempting to move the
// component's outer element via native browser APIs.<br>
// The content of SmartClient components' DOM elements should also not be directly 
// manipulated using native browser APIs - standard methods such as 
// +link{canvas.setContents()}, +link{canvas.addChild()}, +link{canvas.removeChild()},
// +link{canvas.markForRedraw()} and +link{canvas.redraw()} should be used instead.
// <P>
// In some cases, the element returned may match the element returned by 
// +link{canvas.getContentElement()}, but this will not always be the case.
// <P>
// If the widget is undrawn, this method will return <code>null</code>.
//
// @return (DOMElement) The outer DOM element for a drawn Canvas.
// @visibility external
//<
getOuterElement : function () {
    return this.getClipHandle();
},

//> @method canvas.getContentElement() [A]
// Returns the DOM element for this Canvas which contains the +link{canvas.contents}, or
// for +link{canvas.parentElement,parent components}, the DOM elements for any drawn
// children. This method is provided for the 
// very rare cases where a programmer needs to examine the DOM hierarchy created by
// a drawn SmartClient component.
// <P>
// Direct manipulation of the DOM elements created by SmartClient components
// is not supported. SmartClient components should be rendered or cleared using
// standard methods such as +link{canvas.draw()}, +link{canvas.clear()}. If direct
// integration with existing DOM structures is required, this should be achieved via the
// +link{canvas.htmlElement} attribute, rather than by attempting to move the
// component's outer element via native browser APIs.<br>
// The content of SmartClient components' DOM elements should also not be directly 
// manipulated using native browser APIs - standard methods such as 
// +link{canvas.setContents()}, +link{canvas.addChild()}, +link{canvas.removeChild()},
// +link{canvas.markForRedraw()} and +link{canvas.redraw()} should be used instead.
// <P>
// In some cases, the element returned may match the element returned by
// +link{canvas.getOuterElement()}, but this will not always be the case.
// <P>
// If the widget is undrawn, this method will return <code>null</code>.
//
// @return (DOMElement) The outer DOM element for a drawn Canvas.
// @visibility external
//<
getContentElement : function () {
    return this.getHandle();
},

//> @method canvas.getScrollHandle() (A)
//      If we're scrolling by setting the native scroll position on some DOM element this
//      method gives us a pointer to that element.
//
//      @group  handles
//      @return (handle)  scroll handle of the DOM element
//<

getScrollHandle : function () {
    // default implementation uses this widget's clipHandle.
    return this.getClipHandle();
},

// _getURLHandle()
// Get the handle of the IFrame we used to load content (when using contentsURL and
// contentsType:"page")
_getURLHandle : function () {
    if (!this.containsIFrame()) return null;
    var handle = this.getHandle();

    if (!handle) return null;
    // The IFRAME should be rendered as the only child of the handle. Do a sanity check to
    // verify we are looking at an IFRAME element.
    handle = handle.firstChild;
    if (handle && handle.tagName && (handle.tagName.toLowerCase() == "iframe")) return handle

    return null;
},

//>FocusProxy
//> @method canvas._getFocusProxyHandle() (I)
//
//      @group  handles
//      @return (handle) - handle for the 'focusProxy' button.  <code>null</code> if
//                         this._useFocusProxy is false.
//<
_getFocusProxyHandle : function () {
    if (!this._useFocusProxy || !this._hasFocusProxy) return null;

    if (!this._focusProxy) {
        var elementId = this.getCanvasName() + "__focusProxy";
        this._focusProxy = this.getDocument().getElementById(elementId);
    }

    return this._focusProxy;
},

//> @method canvas._getFocusProxyParentHandle() (I)
//
//      @group  handles
//      @return (handle) - handle for the parent div for the 'focusProxy' button.
//                         <code>null</code> if this._useFocusProxy is false.
//<
_getFocusProxyParentHandle : function () {
    if (!this._useFocusProxy) return null;
    if (!this._focusProxy) this._focusProxy = this._getFocusProxyHandle();

    return (this._focusProxy != null ? this._focusProxy.parentNode : null);
},
//<FocusProxy


//>	@method	canvas.getStyleHandle()	(A)
//		Return the style handle for this canvas.
//		 This is what we use to set some of the physical properties
//		 of canvases, such as the visibility, left, etc.
//
//		@group	handles
//		@return	(handle)	style handle to this layer
//<
getStyleHandle : function () {
    if (!this._styleHandle) {
        
        var clipHandle = this.getClipHandle();
        this._styleHandle = (clipHandle != null ? clipHandle.style : null);
    }
    return this._styleHandle;
},

// --------------------------------------------------------------------------------------------

//>	@method	canvas.setUpEvents()	(A)
//			set up the handle for this canvas to respond properly to mouse/keyboard events
//		@group	events
//<
// On all browsers, all Canvii write an "eventProxy" attribute into their DOM representation
// (DIV element), which the isc.EventHandler uses to route events to the Canvas which drew the
// DOM elements.
setUpEvents : function () {
    // register to receive drop events, if necessary
    if (this.canAcceptDrop) this.ns.EH.registerDroppableItem(this);
},

// Creating children
// --------------------------------------------------------------------------------------------

// make sure that everything in the children array is a canvas, and has us as its
// parentElement.  if either is not true, call addChild() to add it as a proper child
_instantiateChildren : function (children) {

	// start with a fresh children array,
	//		in case any children add peers (which should appear directly after that child).
    // NOTE: creating a fresh Array here is also key when Canvas.children is set as an
    // inherited property, because each Canvas needs to have a unique children Array
    if (!children) children = this.children;
    if (!children) return;
	this.children = [];

	for (var i = 0, child; i < children.length; i++) {
		child = children[i];

        if (!child) continue;

		// if the child is not a canvas, or doesn't recognize us as its parent
		// call addChild() to create it and add it to our list of children
		if (!isc.isA.Canvas(child) || child.parentElement != this) {
			this.addChild(child);

		// otherwise, it's already been set up correctly (by a previous call to addChild())
		//	so we'll just add it to our children array
		} else {
			this.children.add(child);
		}
	}
},


_$autoChildPrefix:"autoChild:",
_$spacerChildPrefix: "spacer:",
_lazyAutoChildCreate : function (name) {
    name = name.substring(this._$autoChildPrefix.length);

    //this.logWarn("lazy creation of autoChild: " + name);

    // try to figure out whether this autoChild should be created by the current widget, or, if
    // this widget is itself an autoChild, by it's creator
    var defaultsName = this._getDefaultsName(name),
        propertiesName = this._getPropertiesName(name);
    var creator = this[defaultsName] || this[propertiesName] ? this :
                    isc.isA.Canvas(this.creator) && 
                            (this.creator[defaultsName] || this.creator[propertiesName]) 
                            ? this.creator : this;

    if (isc.isA.Canvas(creator[name])) return creator[name];

    // NOTE: when creating autoChildren in this method, we want:
    // - unconditional creation, unlike addAutoChild()
    // - no adding to parent (callers generally have a parent in mind), unlike addAutoChild()
    // - do want to set up this[name], like addAutoChild()
    return (creator[name] = creator.createAutoChild(name));
},

// create or find a Canvas based on the passed string or properties, or return it if it's
// already a Canvas.  Used to allow canvas.children, layout.members, window.items, etc to
// accept various standard ways of specifying Canvii
createCanvas : function (canvas) {
    if (isc.isA.Canvas(canvas)) return canvas;

    if (canvas == null) return;

    if (isc.isA.String(canvas)) {
        // the "autoChild:[childName]" format allows lazy instantiation of autoChildren from eg
        // section.items or tab.pane
        if (isc.startsWith(canvas, this._$autoChildPrefix)) {
            return this._lazyAutoChildCreate(canvas);
        }
        if (isc.startsWith(canvas, this._$spacerChildPrefix)) {
            var spacerLength = canvas.substring(this._$spacerChildPrefix.length);
            var lengthAttribute = "width";
            if (this.orientation == isc.Layout.VERTICAL) lengthAttribute = "height";
            var props = {autoDraw: false};
            props[lengthAttribute] = spacerLength;
            return isc.LayoutSpacer.create(props);
        }

        // otherwise assume the id of a global widget
        return window[canvas];
    }

    var autoChildName = canvas.autoChildName;
    if (autoChildName) {
        // NOTE: we want just creation here, not adding to parent, as addAutoChild would do
        return this[autoChildName] = this.createAutoChild(autoChildName, canvas);
    }

    // new child provided as a properties block - create it
    var cons = canvas._constructor;
    // if constructor isn't provided or doesn't name a class, default to Canvas
    if (cons == null || isc.ClassFactory.getClass(cons) == null) {
        this.logWarn("Unable to create canvas of type '" + cons + "' - no such class in runtime. Will default to Canvas.");
        if (isc.isA.String(cons) && cons.contains(".")) {
            this.logWarn("Did you make the SmartGWT class reflectable? See http://www.smartclient.com/smartgwt/javadoc/com/smartgwt/client/docs/Reflection.html");
        }
        cons = isc.Canvas;
    }
    canvas._constructor = null;

    // prevent autoDraw
    canvas.autoDraw = false;

    return isc.ClassFactory.newInstance(cons, canvas);
},

createCanvii : function (canvii) {
    if (canvii == null) return;
    for (var i = 0; i < canvii.length; i++) {
        canvii[i] = this.createCanvas(canvii[i]);
    }
    return canvii;
},

// setEventProxy() - update the eventProxy for this widget at runtime
setEventProxy : function (newProxy) {
    // clear any back-references from current eventProxy
    var oldProxy = this.eventProxy;
    if (oldProxy == newProxy) return;

    if (oldProxy != null) {
        oldProxy._proxiers.remove(this);
        // Clear the eventProxy pointer from the DOM object
        
        if (this.isDrawn()) {
            if (this.getHandle() != null) this.getHandle().eventProxy = null;
            if (this.getClipHandle() != this.getHandle()) this.getClipHandle().eventProxy = null;
        }
    }

    // set this.eventProxy to the newProxy passed in (may be null, in which case we clear out
    // the eventProxy).
    this.eventProxy = newProxy;

    if (newProxy != null) {
        if (!isc.isA.Canvas(newProxy)) {
            this.logWarn("setEventProxy() passed invalid eventProxy - clearing this property");
            this.eventProxy = null;
        } else {
            if (newProxy._proxiers == null) newProxy._proxiers = [];
            newProxy._proxiers.add(this);
        }
    }
    // Have to redraw, so eventHandling doesn't get confused about what the eventproxy is
    // Make this an immediate redraw, so *Any* subsequent events go through to the appropriate
    // proxy.
    if (this.isDrawn()) this.redraw("eventProxy updated");
},

// clearEventProxy() - clear this widget's eventProxy at runtime
clearEventProxy : function () {
    this.setEventProxy();
},

// Adding and Removing Children and Peers
// --------------------------------------------------------------------------------------------

//>	@method	canvas.addChild()   ([])
// Adds newChild as a child of this widget, set up a named object reference (i.e., this[name])
// to the new widget if name argument is provided, and draw the child if this widget has been
// drawn already.
// <P>
// If newChild has a parent it will be removed from it. If it has a master, it will be detached
// from it if the master is a child of a different parent. If newChild has peers, they'll be
// added to this widget as children as well.
//
//  @visibility external
//	@group	containment
//	@param	newChild		(canvas)	new child canvas to add
//	@param	[name]			(string)	name to assign to child (eg: this[name] == child)
//  @param  [autoDraw]      (Boolean)   if false, child will not automatically be drawn (only
//                                          for advanced use)
//	@return	(canvas)	the new child, or null if it couldn't be added
//<
addChild : function (newChild, name, autoDraw) {

    if (isc._traceMarkers) arguments.__this = this;
	if (!newChild) return null;	// just to be safe

    if (newChild == this) {
        this.logWarn("Attempt to add a child to itself");
        return;
    }

	//this.logInfo("addChild() called on " + newChild + " : parent is drawn() " + this.isDrawn()
	//			+ " : child is " + (isA.Canvas(newChild) ? (newChild.isDrawn() ? "drawn " : "undrawn ") + newChild.Class
	//													 : "object literal " + Echo.asString(newChild)), "drawing");

    // instantiate the child on the fly if it hasn't been created yet (autodraw is suppressed)
	if (!isc.isAn.Instance(newChild)) newChild = this.createCanvas(newChild);

	if (!isc.isA.Canvas(newChild)) {
		//>DEBUG
		this.logWarn("addChild(): trying to install a non-canvas as a child.  Returning.");
		//<DEBUG
		return null;
	}

	// if newChild already recognizes this canvas as its parent, bail
	if (newChild.parentElement == this) return newChild;

    var wasDrawn = newChild.isDrawn();

	// remove the child from its old parent, if any
	if (newChild.parentElement) newChild.deparent(name);

    // Remove the child from the top level canvas list - it's no longer a top level canvas
    isc.Canvas._removeFromTopLevelCanvasList(newChild);

    // drop the drawContext to ensure the child does not try to draw in some arbitrary DOM
    // location instead of inside it's new parent
    if (newChild.drawContext) newChild.drawContext = null;
    if (newChild.htmlElement) newChild.htmlElement = null;

	// attach the child to its new parentElement (this canvas) and topElement
	newChild.parentElement = this;
	newChild.topElement = (this.topElement || this);
	// update topElement for the child's children, if any
    // (This method will recursively be called on each child / descendant)
	newChild._updateChildrenTopElement();

	if (name) this[name] = newChild;
	if (!this.children) this.children = [];
	// the following conditional allows for direct initialization of the children[] property
	if (!this.children.contains(newChild)) this.children.add(newChild);


	// detach the child from its master, if the master's parent is different
	var childsMaster = newChild.masterElement;
	if (childsMaster && childsMaster.parentElement != this) {
		childsMaster.peers.remove(newChild);
		if (childsMaster[name] == newChild) childsMaster[name] = null;
		newChild.masterElement = null;
	}

	// add the child's peers, if any, as children of this parent
	// it's important that this is done *after* newChild is attached to its parent, so the
    // peers don't think they're being moved to a different parent and break their peer/master
    // link
	if (newChild.peers) {
        for (var i = 0; i < newChild.peers.length; i++) this.addChild(newChild.peers[i]);
    }

	// if the child had already been drawn (therefore outside of this canvas), clear it
	if (newChild.isDrawn()) newChild.clear();

    // If the page isn't done loading, and we had to clear in order to reparent, warn the
    // developer - chances are they failed to set autoDraw:false
    if (wasDrawn && !this.warnAboutClear && !isc.Page.isLoaded()) {
        this.logWarn("Adding already drawn widget:" + newChild.getID() + " to new parent:" +
                    this.getID() + ". Child has been cleared so it can be drawn inside the new " +
                    "parent. This may be a result of autoDraw being enabled for the child.");
    }

    // Calculate sizes that are expressed as a percentage of parent size, if necessary; this is
    // done after clear so that unnecessary redraws are avoided.  If the parent has not yet
    // drawn, no need, as this happens as part the call to layoutChildren() during draw.
    if (this.isDrawn()) newChild._resolvePercentageSize();

    // Ensure that if any clickmasks are showing our child is at the same level as we are
    // wrt them.
    
    var EH = this.ns.EH;
    if (EH.clickMaskUp()) {

        var CMIDs = EH.getAllClickMaskIDs();
        for (var i = CMIDs.length -1; i >= 0; i--) {
            var parentMasked = EH.targetIsMasked(this, CMIDs[i]);
            if (!parentMasked) {
                EH.addUnmaskedTarget(newChild, CMIDs[i]);
                // We're iterating down from the top - once a widget is over one mask it's also
                // over any masks below that one. Therefore we don't need to keep iterating
                // down to the bottom adding unmasked targets.
                break;
            } else {
                // If we're masked by our child is not, mask the child.
                var childMasked = EH.targetIsMasked(newChild, CMIDs[i]);
                if (!childMasked) EH.maskTarget(newChild, CMIDs[i]);
            }
        }
    }

    // for very advanced callers, support not drawing the child automatically
    if (autoDraw == false || newChild._dontDrawOnAddChild) {
        // support one-time flag.  Kind of a hack, but there are many codepaths that ultimately
        // call addChild()
        newChild._dontDrawOnAddChild = null;
        return newChild;
    }

    
    var tabIndexManaged = false,
        autoTabIndex = (newChild._autoTabIndex || !newChild.tabIndex);

    if (isc.isA && isc.isA.Layout &&  autoTabIndex &&
        (newChild._canFocus() || (newChild.children != null && newChild.children.length > 0)))
    {
        var currentChild = newChild;
        while (currentChild.parentElement) {
            if (isc.isA.Layout(currentChild.parentElement) && currentChild.parentElement.isDrawn())
            {
                currentChild.parentElement.updateMemberTabIndex(currentChild);
                if (currentChild.parentElement == this) tabIndexManaged = true;
            }
            currentChild = currentChild.parentElement;
        }
    }

	// if we're not drawn yet, we'll wait to draw the child when we draw.  If we've been drawn,
    // tell the child to draw as well -- unless it has a master, in which case it's a peer of
    // another child, and that other child will draw it.
	if (this.isDrawn() && !newChild.masterElement) {
        //>DEBUG
        if (this.logIsDebugEnabled(this._$drawing)) {
            this.logInfo("child added to already drawn parent: " +
                         (isc.Page.isLoaded() ? "page loaded, will draw immediately" :
                                            "page not loaded, will defer child drawing"),
                         "drawing");
        }
        //<DEBUG

        // if the user has not specified a tabIndex for the child, slot it into the tab order
        // after the previous canFocus:true child for this widget, with an auto-allocated tab
        // index.
        //
        // Note: this widget is drawn, so we can assume that the tab index will have been
        // assigned, and this._autoTabIndex will have been set to true if the tab index was
        // auto-allocated.
        // We can assume the same for any focusable children of this widget.
        
        
        if (!tabIndexManaged && newChild._shouldManageTabIndex() && autoTabIndex) {

            var lastChild;

            if (this.children.length  > 1) {
                // find the previous child with an auto-allocated tab index
                for (var i = this.children.length -2; i >=0 ; i--) {
                    if (this.children[i]._canFocus() && this.children[i]._autoTabIndex) {
                        lastChild = this.children[i];
                        break;
                    }
                }
            }
            // if we didn't find a focusable (and _autoTabIndex) previous child, see if this widget
            // is focusable and has an auto-allocated tab index
            if (lastChild == null && this._canFocus() && this._autoTabIndex) {
                lastChild = this;
            }

            // slot this child after the last child with an auto-allocated tab index
            if (lastChild != null) newChild._setTabAfter(lastChild);

        }

        // Draw the child, and adjust overflow to account for any changes
        // NOTE: draw() may be delayed until after page load for some older browsers
        
        newChild.draw();
        this.adjustOverflow("addChild");
	}

	return newChild;
},


_updateChildrenTopElement : function () {
    // if a dataPath is specified, values may be managed by a valuesManager applied to
    // an ancestor widget.
    // Re-Run 'setDataPath()' when the ancestor hierarchy changes
    // Note that for non dataBound components this no-ops
    
    if (this.dataPath) this.setDataPath(this.dataPath);

    var children = this.children;
    if (!children || children.length == 0) return;
    for (var i = 0; i < children.length; i++) {
        var child = children[i];
        child.topElement = this.topElement;
        child._updateChildrenTopElement();
    }
},

//>	@method	canvas.reparent()
//		Make this canvas have the same parent as some other canvas.
//		Works even if this is a top-level object.
//
//		@return	(boolean)	true == reparenting actually occurred.
//		@group	containment
//<
reparent : function (newSibling) {

    // bail if we're trying to reparent to ourselves!
	if (this.getID() == newSibling.getID()) return false;

    // If they have the same ISC AND Native parents already, just bail
    if ((this.parentElement == newSibling.parentElement) &&
        this.getClipHandle() && newSibling.getClipHandle() &&
        (this.getClipHandle().parentNode == newSibling.getClipHandle().parentNode)) {
        return false;
    }

    // Note - to handle relative positioning, etc. properly, we need to ensure that this
    // widget ends up with the same DOM parent as the newSibling - regardless of whether it is
    // an ISC parentElement

    // Ensure this will be drawn next to the newSibling
    this._adjacentHandle = newSibling.getClipHandle();

	if (newSibling.parentElement) {
        // join our sibling's parent (will handle setting up ISC relationships, and drawing)
		newSibling.parentElement.addChild(this);
	} else {
        // Ditch any existing parent
		if (this.parentElement) this.deparent();
        // Or clear() so we can redraw
        else this.clear();

        // and draw anew next to the newSibling
		this.draw();
	}
	return true;
},

// NOTE: child/peer removal:
// - you can call either deparent or removeChild to accomplish child removal (likewise
//   depeer/removePeer)
// - deparented/childRemoved and depeered/peerRemoved are clean notification points that are
//   guaranteed to be called
// - removePeer/removeChild are guaranteed to be called, so can be used as an override point in
//   advanced widgets.  deparent/depeer are *not* guaranteed to be called - with a set of flags we
//   could make this possible without infinite recursion

//>	@method	canvas.removePeer()
// Remove a peer from this Canvas
// @group containment
// @param peer (Canvas) Peer to be removed from this canvas
// @param [name] (String) If this peer was assigned a name when added via addPeer(), it should
//                        be passed in here to ensure no reference is kept to the peer
// @visibility external
//<
removePeer : function (peer, name) {
    if (peer == null) return;

    var peers = this.peers, index;
    //>DEBUG
    if (!peers || (index = peers.indexOf(peer)) == -1) {
        this.logWarn("Attempt to remove peer: " + peer + " from Canvas that is not its master");
        return;
    }
    //<DEBUG

    if (this.parentElement) {
        this.parentElement.removeChild(peer);
    }

    // remove our links to the peer
    peers.removeAt(index);
	if (this[name] == peer) this[name] = null;
    // remove peer's link to us
    peer.masterElement = null;
    // fire notifications
    if (peer.depeered) peer.depeered(this, name);
    if (this.peerRemoved) this.peerRemoved(peer, name);
},

//>	@method	canvas.depeer()
// Make this Canvas no longer a peer of its master
// @group containment
// @visibility external
//<
// NOTE: name intentionally undocumented; AutoChild approach is better if you want name assignment
depeer : function (name) {
    if (!this.masterElement) return;
    this.masterElement.removePeer(this, name);
},
//>	@method	canvas.deparent()
// Remove this canvas from its parent if it has one.
// @group containment
// @visibility external
//<
// NOTE: name intentionally undocumented; AutoChild approach is better if you want name assignment
deparent : function (name) {
    // name intentionally undocumented
    if (!this.parentElement) return;
    this.parentElement.removeChild(this, name);
},

//>	@method	canvas.removeChild()
// Remove a child from this parent.
// @param child (Canvas) Child canvas to remove from this parent.
// @param [name] (string) If the child canvas was assigned a name when added via addChild(), it
//                        should be passed in here to ensure no reference is kept to the child
// @group containment
// @visibility external
//<

removeChild : function (child, name) {
    if (isc._traceMarkers) arguments.__this = this;
    if (child == null) return;

    var children = this.children, index;
    //>DEBUG
    if (!children || (index = children.indexOf(child)) == -1) {
        this.logWarn("Attempt to remove child: " + child + " from Canvas that is not its parent");
        return;
    }
    //<DEBUG

    // remove our links to the child
    children.removeAt(index);
    if (this[name] == child) this[name] = null;
    // remove the child's HTML.  NOTE needs to happen before parentElement/topElement are
    // removed since clear() fires key notifications that cause the parent to adjust to the now
    // undrawn child.
	if (child.isDrawn()) child.clear();
    // remove child's links to us
	delete child.parentElement;
	delete child.topElement;

    // Add the child to the top level list of canvii
    isc.Canvas._addToTopLevelCanvasList(child);

	// deparent any peers of the child, which are also our children
	if (child.peers) child.peers.map("deparent");
    // fire notifications
    if (child.deparented) child.deparented(this, name);
    if (this.childRemoved) this.childRemoved(child, name);
},



//>	@method	canvas.addPeer()    ([])
// Adds newPeer as a peer of this widget (also making it a child of this widget's parent, if
// any), set up a named object reference (i.e., this[name]) to the new widget if name is
// provided, and draw the peer if this widget has been drawn already.<br>
// <P>
// The widget to be added as a peer will be removed from its old master and/or parent, if any,
// and it will be added as a child to the parent of this canvas (if any)
//
// @param newPeer (Canvas) new peer widget to add
// @param [name] (String) name to assign to peer (eg: this[peer] == child)
// @param [autoDraw] (Boolean) if true, peer will not automatically be drawn (only
//                             for advanced use)
// @param [preDraw] (Boolean) if true, when draw is called on the master widget, the peer
//                            will be drawn before the master
// @return (Canvas) the new peer, or null if it couldn't be added
//
// @group containment
// @visibility external
//<
addPeer : function (newPeer, name, autoDraw, preDraw) {
	if (!newPeer) return null;	// just to be safe

    // instantiate the peer on the fly if it hasn't been created yet (autodraw is suppressed)
	if (!isc.isAn.Instance(newPeer)) newPeer = this.createCanvas(newPeer);

	// if this peer is marked for 'predrawing', hang the '_drawBeforeMaster' flag onto the peer, so
    // that when draw is called on the master, this peer gets drawn first.
    // If the master element is already drawn, and this flag is set, we'll call 'redraw' on the
    // master element when this newPeer gets drawn (below).
    if (preDraw == true) newPeer._drawBeforeMaster = true;

    // if newPeer already recognizes this canvas as its master, bail
	if (newPeer.masterElement == this) return null;

	// remove the peer from its old master, if any
	if (newPeer.masterElement) newPeer.depeer(name);

	// attach the peer to its new master (this canvas)
	newPeer.masterElement = this;
	if (name) this[name] = newPeer;
	if (!this.peers) this.peers = [];
	// the following conditional allows for direct initialization of the peers[] property
	if (!this.peers.contains(newPeer)) this.peers.add(newPeer);

	// attach the peer to the same parent as us
	if (this.parentElement) {
		// make the peer a child of our parent (removes peer from its old parent, if any)
		this.parentElement.addChild(newPeer, name);
	} else if (newPeer.parentElement) {
		// or detach the peer from its old parent if it has one
		newPeer.deparent();
	}

    // If we're keeping our opacity in synch with that of our peers, update in now.
    if (newPeer._setOpacityWithMaster && (newPeer.opacity != this.opacity))
        newPeer.setOpacity(this.opacity);

    // If we're showing / hiding with our peers ensure peers visibility is in synch with ours
    if (newPeer._showWithMaster && (newPeer.visibility != this.visibility)) {
        newPeer.setVisibility(this.visibility);
    }

	// If snapTo or snapEdge are set, recalc peer position
	if (newPeer.snapTo || newPeer.snapEdge) newPeer._resolvePercentageSize();

    var EH = this.ns.EH;
    if (EH.clickMaskUp()) {
        var CMIDs = EH.getAllClickMaskIDs();
        for (var i = CMIDs.length -1; i >= 0; i--) {
            var masterMasked = EH.targetIsMasked(this, CMIDs[i]);
            if (!masterMasked) {
                // addUnmaskedTarget will automatically unmask children and peers of the
                // new peer recursively.
                EH.addUnmaskedTarget(newPeer, CMIDs[i]);
                // We're iterating down from the top - once a widget is over one mask it's also
                // over any masks below that one. Therefore we don't need to keep iterating
                // down to the bottom adding unmasked targets.
                break;
            } else {
                // If we're masked but our peer is not, mask the peer
                var peerMasked = EH.targetIsMasked(newPeer, CMIDs[i]);
                if (!peerMasked) EH.maskTarget(newPeer, CMIDs[i]);
            }
        }
    }

    // for very advanced callers, support not drawing the child automatically
    if (autoDraw == false) return newPeer;

	// if we've been drawn and the peer hasn't, tell the peer to draw as well
	if (this.isDrawn() && !newPeer.isDrawn()) {
		newPeer.draw();
        // If the '_drawBeforeMaster' flag has been set on the new peer, force a redraw of the
        // master after the peer is first drawn.
        // We do this because the _drawBeforeMaster flag implies that the master element expects
        // the peer to have been drawn when it itself is drawn, for example, so it can make use
        // of the peer's drawn size in its own getInnerHTML() method.
        // This redraw therefore gives the master a chance to rebuild its HTML after the peer
        // has been drawn.
        if (newPeer._drawBeforeMaster) this.redraw();
	}

	return newPeer;
},

// SnapTo / SnapEdge positioning
// ---------------------------------------------------------------------------------------

//> @method canvas.setSnapTo()  ([])
// Set the snapTo property of this canvas, and handle repositioning.
//
// @group positioning
// @param snapTo (string) new snapTo value
// @visibility external
//<
setSnapTo : function (snapTo) {
    this.snapTo = snapTo;
    this.parentResized();
},

//> @method canvas.getSnapTo()  ([])
// Return the snapTo value of this object
//
// @return (string) snapTo
// @group positioning
// @visibility external
//<
getSnapTo : function () {
    return this.snapTo;
},

//> @method canvas.setSnapEdge()  ([])
// Set the snapEdge property of this canvas, and handle repositioning.
//
// @param snapEdge (string) new snapEdge value
// @group positioning
// @visibility external
//<
setSnapEdge : function (snapEdge) {
    this.snapEdge = snapEdge;
    this.parentResized();
},

//> @method canvas.getSnapEdge()  ([])
// Return the snapEdge value of this object
//
// @return (string)    snapEdge
// @group  positioning
// @visibility external
//<
getSnapEdge : function () {
    return this.snapEdge;
},

//>EditMode
// provide addChild and removeChild as the adder/remover function for the "children" field (not
// mechanically guessable by naming conventions)
getFieldMethod : function (type, fieldName, verb) {
    //this.logWarn("getMethod, field: " + fieldName);
    if (fieldName == "children") {
        if (verb == "add") return "addChild";
        if (verb == "remove") return "removeChild";
    }
    return this.Super("getFieldMethod", arguments);
},
//<EditMode

// Canvas hierarchy
// --------------------------------------------------------------------------------------------

//>	@method	canvas.getParentElements()
// Returns an array of object references to all ancestors of this widget in the containment
// hierarchy, starting with the direct parent and ending with the top element.
// @return (Array of Canvas) array of parents, closest first; empty array if no parents
// @group containment
// @visibility external
//<
getParentElements : function () {
	var list = [],
		parent = this.parentElement;
	// while there are parents
	while (parent) {
		// add them to the list
		list.add(parent);
		parent = parent.parentElement;
	}
	// return the list
	return list;
},

//>	@method	canvas.contains()	([A])
//      Returns true if element is a descendant of this widget (i.e., exists below this widget in
//      the containment hierarchy); and false otherwise.
//  @visibility external
//  @group	containment
//	@param	canvas	(canvas)    the canvas to be tested
//  @param  [testSelf] (Boolean) If passed this method will return true if the the canvas
//                               parameter is a pointer to this widget.
//	@return	(Boolean)	true if specified element is a descendant of this canvas; false otherwise
//<
contains : function (canvas, testSelf) {
    if (!testSelf && canvas) canvas = canvas.parentElement;
	while (canvas) {
		if (canvas == this) return true;
		canvas = canvas.parentElement;
	}
	return false;
},

// Is this element the parent of the child passed in, AND the child inherits its visibility from
// this parent?
_isVisibilityAncestorOf : function (child) {
    var target = child;

    while (target) {
        if (target == this) return true;
        var inherits = (target.visibility == isc.Canvas.INHERIT);
        if (!inherits) return false;
        target = target.parentElement;
    }
    return false;
},

// get total number of recursively contained children
getChildCount : function () {
    if (this.children == null) return;
    return this.children.map("getChildCount").sum() + this.children.length;
},


// ClickMask
// --------------------------------------------------------------------------------------------

//>	@method Canvas.showClickMask()
// Show a clickMask over the entire screen that intercepts mouse clicks and fires some action.
// The mask created will be associated with this canvas - calling this method multiple times
// will not show multiple (stacked) clickMasks if the mask associated with this canvas is
// already up.<br><br>
//
// The clickMask useful for modal dialogs, menus and similar uses, where any click outside of
// some Canvas should either be suppressed (as in a modal dialog) or just cause something (like
// dismissing a menu).
//
// @group	clickMask
//
// @param	clickAction	    (callback)	action to fire when the user clicks on the mask
// @param	mode        (ClickMaskMode)	whether to automatically hide the clickMask on mouseDown
//                                      and suppress the mouseDown event from reaching
//                                      the target under the mouse
// @param   unmaskedTargets (widget | array of widgets)
//  initially unmasked targets for this clickMask. Note that if this is a
//  <code>"hard"</code> mask, unmasked children of masked parents are not supported
//  so any non-top-level widgets passed in will have their parents unmasked.
//  Children of masked parents can never be masked.
// @return  (string)    clickMask ID
// @see     canvas.hideClickMask()
// @visibility external
//<
showClickMask : function (clickAction, mode, unmaskedTargets) {
    
    var ID = this.getID();
    if (!this.ns.EH.clickMaskUp(ID)) {
        return this.ns.EH.showClickMask(clickAction, mode, unmaskedTargets, ID);
    }
},

//>	@method	Canvas.hideClickMask()
// Hides the click mask associated with this canvas.
//		@group	clickMask
//      @param  [ID]    (string) optional ID of specific clickMask to hide. If not passed,
//                      defaults to hiding the click mask associated with this widget only.
//      @visibility external
//      @see canvas.showClickMask()
//<
hideClickMask : function (ID) {
    if (ID == null) ID = this.getID();
    if (this.ns.EH.clickMaskUp(ID)) this.ns.EH.hideClickMask(ID);
},


//>	@method	Canvas.clickMaskUp()
// Determines whether a clickmask is showing
//@group clickMask
// @param [ID] (string) optional ID of specific clickMask to check. If not passed,
//                      checks for the click mask associated with this widget only.
// @return (Boolean) whether or not a clickmask is showing
// @visibility external
// @see canvas.showClickMask()
//<
clickMaskUp : function (ID) {
    if (ID == null) ID = this.getID();
    return this.ns.EH.clickMaskUp(ID);
},


//>	@method	Canvas.unmask()
// If a click mask is currently covering this widget, unmask it.
// @group	clickMask
// @param  [mask]    (string) optional ID of specific clickMask for which this widget should
//              be unmasked. If not passed, unmasks target wrt all clickMasks.
// @visibility clickMask
//<
unmask : function (mask) {
    this.ns.EH.addUnmaskedTarget(this, mask);
},

//>	@method	Canvas.mask()
// Ensure this widget is obscured by a currently visible clickMask.
// @group	clickMask
// @param  [mask]    (string) optional ID of specific clickMask to put this widget behind.
//                            If not passed, masks target wrt all clickMasks.
// @visibility clickMask
//<
mask : function (mask) {
    this.ns.EH.maskTarget(this, mask);
},

//>	@method	Canvas.isMasked()
// Is this widget currently obscured by a currently visible clickMask.
// @group	clickMask
// @param  [mask]    (string) optional ID of specific clickMask to test. If not passed, will
//                          return true if this canvas is masked by any visible clickMask.
// @visibility clickMask
//<
isMasked : function (mask) {
    return this.ns.EH.targetIsMasked(this, mask);
},

// Helper method - are we covered by a hard (auto-hide:false) clickMask?
_isHardMasked : function () {
    var masks = isc.EH.clickMaskRegistry;
    if (!masks || masks.length == 0) return false;

    for (var i = masks.length-1; i >= 0; i--) {
        var mask = masks[i];
        // If we're unmasked and haven't already hit a hard mask, we're not hard masked
        if (!this.isMasked(mask)) return false;
        // If we hit a hard mask, we are hard masked
        if (isc.EH.isHardMask(mask)) return true;
    }
    // In this case we didn't hit a hard mask, so any masks above us must be soft.
    return false;
},

// Component level masking
// ----------------------------------------------------------------------------------------------
// Support for masking children of this widget only
showComponentMask : function (maskProperties) {
    if (!this.componentMask) {
        this.componentMask = this.addAutoChild(
            "componentMask",
             // mark as disabled - automatically will kill events and not allow bubbling
             isc.addProperties({}, maskProperties, {disabled:true,
             autoDraw:false,
             // resizeWithMaster / moveWithMaster will be true by default
             _setOpacityWithMaster:false
            }),
            isc.Canvas
        );
        this.componentMask.setRect(this.getOffsetLeft(), this.getOffsetTop(),
                                   this.getVisibleWidth(), this.getVisibleHeight());
        this.addPeer(this.componentMask);
    } else if (!this.componentMask.isDrawn()) this.componentMask.draw();

    this.disableKeyboardEvents(true, true);
},

// Enable this to make the component mask visible by default
/*
componentMaskDefaults:{
    backgroundColor:"black",
    opacity:20
},
*/

hideComponentMask : function () {
    if (this.componentMask) this.componentMask.clear();
    this.disableKeyboardEvents(false, true);
},


// Widget Positioning and Sizing Methods
// --------------------------------------------------------------------------------------------
// Note on positioning coordinate systems:
//
//  When describing left / top positions of widgets, there are a few distinct possibilities for
//  the coordinate system you're referring to:
//  1 - Specified widget coordinates
//      - left/top (at init time), getLeft()/getTop(), setLeft()/setTop()
//      For absolutely positioned widgets, this is the distance from the top/left of this
//      widget (measured from outside any border or margin) to the inside of the parent's
//      content.
//      For relatively positioned widgets it is the offset relative to page flow within
//      this widget's parent element.
//  2 - Page level coordinates (getPageLeft() and getPageTop()).
//      This is the absolute offset of the widget from the top / left of the browser window,
//      measured from outside the widget's border and margin.
//      Will match getLeft() / getTop() for absolutely positioned elements at the top level.
//  3 - Canvas level coordinates (getCanvasLeft() / getCanvasTop())
//      This is the absolute offset of the widget from the left / top of its 'parentElement' -
//      the ISC widget defined as it's parent.  Measured from the outside of any border/margin
//      on this widget to the inside of the parent widget's handle - so for
//      absolutely positioned elements will be the same as the specified widget coordinates,
//      and in almost every case will be identical to the result of getOffsetLeft() / top()
//      [As the parent scrolls, this value will not change, like the specified or offset values
//       it is relative to the parent's content rather than floating position on the page].
//  4 - Offset coordinates (getOffsetLeft() and getOffsetTop()).
//      This is the absolute offset of the widget from the left / top of the native DOM
//      offsetParent of the widget (may or may not be a canvas).
//      Value is calculated from the outside of any border / margin of this widget to the
//      inside edge of the offsetParent element.
//      Used internally - should not need to be exposed.


//>	@method	canvas.setRect()    ([])
// Set all four coordinates, relative to the enclosing context, at once.
// <P>
// Moves the widget so that its top-left corner is at the specified top-left coordinates,
// then resizes it to the specified width and height.
//
//      @visibility external
//		@group	positioning, sizing
//		@param	[left]		(number, Array, Object)	new left coordinate, Array of coordinates
//                                                  in parameter order, or Object with left,
//                                                  top, width, height properties.  If an Array
//                                                  or Object is passed, the remaining
//                                                  parameters are ignored
//		@param	[top]		(number)	new top coordinate
//		@param	[width]		(number)	new width
//		@param	[height]	(number)	new height
//      @return (boolean) whether the component's size actually changed
//<
//>Animation
// @param [animating] (boolean) Internal optional parameter passed if we are performing
//  an animated setRect
//<Animation
setRect : function (left, top, width, height, animating) {
    if (isc._traceMarkers) arguments.__this = this;
	if (isc.isAn.Array(left)) {
		top = left[1];
		width = left[2];
		height = left[3];
		left = left[0];
	} else if (left != null && left.top != null) {
        top = left.top;
        width = left.width;
        height = left.height;
        left = left.left;
    }


    //>DEBUG
    if (this.logIsDebugEnabled()) {
        this.logDebug("setRect: " + this.echo({left:left, top:top, width:width, height:height}));
    }
    //<DEBUG

    

    

	// first resize its width and height
    
 	var sizeChanged = this.resizeTo(width, height, animating, true);

    if (sizeChanged) this._settingRect = true;
	// now move the canvas
    
	this.moveTo(left, top, animating, true);
    this._settingRect = null;
    return sizeChanged;
},


//>	@method	canvas.getRect()
//			return the coordinates of this object as rendered in LTWH order
//		@group	positioning, sizing
//
//		@return	(array)		[left, top, width, height]
//<
getRect : function () {
	return [this.getLeft(), this.getTop(), this.getVisibleWidth(), this.getVisibleHeight()];
},

//>	@method	canvas.getLeft()    ([])
//			Return the left coordinate of this object, relative to its enclosing context, in pixels.
//      @visibility external
//		@group	positioning
//		@return	(number)	left coordinate
//<
getLeft : function () {
    var handle = this.getStyleHandle();
    // it hasn't been drawn yet - return this.left
    if (handle == null) return this.left;
    var left = (isc.Browser.isIE ? handle.pixelLeft : parseInt(handle.left));

    
        
        if (this.vscrollOn && this.showCustomScrollbars && this.isRTL()) {
            return left - this.getScrollbarSize();
        }
        return left;
    

},

//>	@method	canvas.getOffsetLeft()
//			Return the offsetLeft coordinate of this object,
//          relative to its (ISC) parent, in pixels.
//		@group	positioning
//
//		@return	(number)	left coordinate
//<
getOffsetLeft : function () {

    // This function returns the absolute position of widgets relative to their clipHandle's
    // offset parent (may be an ISC widget, but could be another HTML element too).
    

    // in this case we're always working with the clipHandle
    var handle = this.getClipHandle();

    
    if (this._isDisplayNone()) handle = null;

    // if we can't get the clip handle, just return the specified left coordinate
    if (handle == null) {
        //>DEBUG NOTE: not logging at WARN priority because it's just too common to manipulate
        // coordinates of an absolutely positioned widget before drawing it.
        if (this.logIsInfoEnabled()) {
            this.logInfo("getOffsetLeft() called before widget is drawn - unable to calculate offset " +
                         "coordinates.  Returning specified coordinates"); //<DEBUG
        }
        return this.left;
    }

    // just return the offsetLeft - this is the absolute position within logical parent element
    // ("offsetParent");
    var offsetLeft = isc.Element.getOffsetLeft(handle);
    if (this.vscrollOn && this.showCustomScrollbars && this.isRTL()) {
        offsetLeft -= this.getScrollbarSize();
    }

    
        return offsetLeft;
    
},

//>	@method	canvas.setLeft()    ([])
//			Set the left coordinate of this object, relative to its enclosing context, in pixels.
//			NOTE: if you're setting multiple coordinates, use setRect(), moveTo() or resizeTo()
//          instead
//      @visibility external
//		@group	positioning
//		@param	left		(number)	new left coordinate
//<
setLeft : function (left) {
	this.moveTo(left, null);
},

//>	@method	canvas.getTop() ([])
//			Return the top coordinate of this object, relative to its enclosing context, in pixels.
//      @visibility external
//		@group	positioning
//      @return (number)    top coordinate
//<
getTop : function () {
    var handle = this.getStyleHandle();
    if (handle == null) return this.top;

    var top = (isc.Browser.isIE ? handle.pixelTop : parseInt(handle.top));
    
        return top;
    
},

//>	@method	canvas.getOffsetTop()
//			Return the offsetTop coordinate of this object,
//          relative to its (ISC) parent, in pixels.
//		@group	positioning
//
//		@return	(number)	top coordinate
//<
getOffsetTop : function () {

    // in this case we're always working with the clipHandle
    var handle = this.getClipHandle();

    
    if (this._isDisplayNone()) handle = null;

    // if we can't get the clip handle, return the specified top
    if (handle == null) return this.top;

    // just return the offsetTop - this is the absolute position
    var top = isc.Element.getOffsetTop(handle);

    

    
        return top;
    
},

//>	@method	canvas.setTop()
// Set the top coordinate of this object, relative to its enclosing context, in pixels.
// <P>
// NOTE: if you're setting multiple coordinates, use setRect() or moveTo() instead
//
//      @visibility external
//		@group	positioning
//		@param	top		(number)	new top coordinate
//<
setTop : function (top) {
	this.moveTo(null, top);
},


//>	@method	canvas.getWidth()
// Return the width of this object, in pixels.
//      @visibility external
//		@group	sizing
//		@return	(number)	width
//<
getWidth : function () {
	return this.width;
},


//>	@method	canvas.setWidth()
// Resizes the widget horizontally to the specified width (moves the right side of the
// widget). The width parameter can be expressed as a percentage of viewport size or as
// the number of pixels.
// <P>
// NOTE: if you're setting multiple coordinates, use resizeTo() or setRect() instead
//
// @visibility external
//		@group	sizing
//
//		@param	width		(number)	new width
//<
setWidth : function (width) {
	this.resizeTo(width);
},


//>	@method	canvas.getHeight()
// Return the height of this object, in pixels.
//      @visibility external
//		@group	sizing
//		@return	(number)	height
//<
getHeight : function () {
	return this._height;
},


//>	@method	canvas.setHeight()
// Resizes the widget vertically to the specified height (moves the bottom side of the
// widget). The height parameter can be expressed as a percentage of viewport size or as
// the number of pixels.
// <P>
// NOTE: if you're setting multiple coordinates, use resizeTo() or setRect() instead
//
// @group sizing
// @param height (number) new height
// @visibility external
//<
setHeight : function (height) {
	this.resizeTo(null, height);
},

//>	@method	canvas.getMinWidth()
// Get the minimum width that this Canvas can be resized to.
// @return	(number)	width
// @group	sizing
//<
getMinWidth : function () {
	return this.minWidth;
},

//>	@method	canvas.getMinHeight()
// Get the minimum height that this Canvas can be resized to.
// @return	(number)	height
// @group	sizing
//<
getMinHeight : function () {
	return this.minHeight;
},

//>	@method	canvas.getMaxWidth()
// Get the maximum width that this Canvas can be resized to.
// @group	sizing
// @return	(number)	width
//<
getMaxWidth : function () {
	return this.maxWidth;
},

//>	@method	canvas.getMaxHeight()
// Get the maximum height that this Canvas can be resized to.
// @return	(number)	height
// @group	sizing
//<
getMaxHeight : function () {
	return this.maxHeight;
},

//>	@method	canvas.getRight()
// Return the right coordinate of this object as rendered, relative to its enclosing context,
// in pixels.
//
// @return (number)	right coordinate
// @group positioning, sizing
// @visibility external
//<
getRight : function () {
	return this.getLeft() + this.getVisibleWidth();
},


//>	@method	canvas.setRight()
// Resizes the widget horizontally to position its right side at the specified coordinate.
// <P>
// NOTE: if you're setting multiple coordinates, use setRect(), moveTo() or resizeTo()
// instead
//
// @param	right		(number)	new right coordinate
//
// @group	sizing
// @visibility external
//<
setRight : function (right) {
	if (isc.isA.Number(right)) {
	    this.resizeTo(right - this.getLeft(), null);
	} else {
	    this.logWarn("setRight() expects an integer value");
	}
},


//>	@method	canvas.getBottom()
// Return the bottom coordinate of this object as rendered, relative to its enclosing context,
// in pixels.
//
// @return	(number)	bottom coordinate
//
// @group positioning, sizing
// @visibility external
//<
getBottom : function () {
	return this.getTop() + this.getVisibleHeight();
},


//>	@method	canvas.setBottom()  ([])
// Resizes the widget vertically to position its bottom edge at the specified coordinate.
// <P>
// NOTE: if you're setting multiple coordinates, use setRect(), moveTo() or resizeTo()
// instead
//
// @param bottom		(number)	new bottom coordinate
// @group sizing
// @visibility external
//<
setBottom : function (bottom) {
	if (isc.isA.Number(bottom)) {
	    this.resizeTo(null, bottom - this.getTop());
	} else {
	    this.logWarn("setBottom() expects an integer value");
	}
},

// Enforcing scroll size and "virtual content"
// Consider the following use cases:
// - A Layout containing a layout-spacer as its last member
// - A parent with a number of children, and the parent wants to create "padding" around
//   those children that behaves like CSS padding.
//   CSS padding as such can't be used because it doesn't affect the positioning of absPos
//   children, and does not wrap around absPos children.
//   The Layout class encounters this use case with the 'layoutMargin' property.
//
// In these cases we have "virtual content" - we know the size we intend the widget's content
// to be, but the browser does not recognize this content and *will not scroll to it*.
//
// We use the enforceScrollSize() / stopEnforcingScrollSize() methods below to workaround this
// issue. When enforceScrollSize() is called, we write an absolutely positioned DIV into the
// widget handle after all other content, giving the handle a truly scrollable area.
// Currently we only make use of these methods in the Layout class if layoutMargin is set
// or the last member is a layoutSpacer.
// We may want to generalize this the Canvas class, for example having a flag that
// automatically calls the 'enforceScrollSize()' on addChild(), childMoved(), childResized
// but we don't have a use-case where this is required at present.

//> @method canvas.enforceScrollSize ()
// Ensure that this widget's scrollable area matches (or exceeds) the dimensions passed in
// @visibility internal
// @param width (number) scroll width
// @param height (number) scroll height
// @see canvas.stopEnforcingScrollSize()
//<

_scrollSizeDivTemplate:["<DIV ID='",
                        null,   // 1: ID
                        "'style='position:absolute;width:1px;height:1px;overflow:hidden;left:",
                        null,   // 3: left
                        "px;top:",
                        null,   // 5: top
                        "px;font-size:0px'>&nbsp;</DIV>"],
_$scrollSizeDiv:"scrollSizeDiv",
//>DEBUG
_$enforceScrollSize:"enforceScrollSize",
//<DEBUG
enforceScrollSize : function (width, height) {
    //>DEBUG
    if(this.logIsDebugEnabled(this._$enforceScrollSize)) {
        this.logDebug("enforcing scroll size:"+ [width, height], "enforceScrollSize");
    }
    //<DEBUG

    if (!this._handleDrawn && !this._drawn) return;

    if (width == null) width = 0;
    if (height == null) height = 0;

    // partial fix/workaround for INFA issue #1857
    
    if (isNaN(width) || isNaN(height) || (!isc.Page.isRTL() && width < 0) || height < 0) {
        this.logWarn("Invalid width or height in Canvas.enforceScrollSize()"
                    +" on component: " + this.getID() + " with sizes: "
                    + [width, height] + this.getStackTrace());
        return;
    }

    if (this._drewClipDiv && isc.Browser.isMoz) {
        
        var handle = this.getHandle();
        handle.style.width = width + isc.px;
        handle.style.height = height + isc.px;
    } else if (this._scrollSizeDiv == null) {
        var template = this._scrollSizeDivTemplate;
        var name = this._getDOMID(this._$scrollSizeDiv);
        template[1] = name;
        template[3] = width-1;
        template[5] = height-1;

        var HTML = template.join(isc.emptyString);
        // We clear this pointer on clear()
        // We also handle redraw
        this._scrollSizeDiv =
            isc.Element.insertAdjacentHTML(this.getHandle(), this._$beforeEnd, HTML, true);
        if (this._scrollSizeDiv == null) {
            this._scrollSizeDiv = document.getElementById(name);
        }
    } else if (!this._enforcingScrollSize || this._enforcingScrollSize[0] != width ||
               this._enforcingScrollSize[1] != height)
    {
        this._scrollSizeDiv.style.left = (width-1) + isc.px;
        this._scrollSizeDiv.style.top = (height-1) + isc.px;
    }
    this._enforcingScrollSize = [width,height];
},

_$minus1px:"-1px",
stopEnforcingScrollSize : function () {
    //>DEBUG
    if(this.logIsDebugEnabled(this._$enforceScrollSize)) {
        this.logDebug("stop enforcing scroll size", "enforceScrollSize");
    }
    //<DEBUG

    delete this._enforcingScrollSize;
    if (!this.isDrawn()) return;

    if (this._drewClipDiv && isc.Browser.isMoz) {
        var handle = this.getHandle();
        handle.style.removeProperty("width");
        handle.style.removeProperty("height");
    } else if (this._scrollSizeDiv) {
        this._scrollSizeDiv.style.left = this._$minus1px;
        this._scrollSizeDiv.style.top = this._$minus1px;
    }
},




//>	@method	canvas.getScrollWidth() ([A])
// Returns the scrollable width of the widget's contents, including children, ignoring
// clipping.
//      @visibility external
//		@group	sizing
//
//		@return	(number)	the scrollable width of the widget's contents
//<
getScrollWidth : function (calculateNewValue) {
    if (isc._traceMarkers) arguments.__this = this;

    
    if (this._deferredOverflow) {
        this._deferredOverflow = null;
        this.adjustOverflow("widthCheckWhileDeferred");
        
    }
    
    if (this.containsIFrame()) {
        return this.getInnerWidth();
    }

    // if we have a cached value and we're not looking for a fresh one, dont calculate a
    // new one
    if (!calculateNewValue && this._scrollWidth != null) return this._scrollWidth;

    var width = 0,
        handle = this.getClipHandle();

    if (handle == null) {
        //>DEBUG
        this.logDebug("No size info available from DOM, returning user-specified size");
        //<DEBUG
        return this.getInnerWidth();
    }

    if (this.allowNativeContentPositioning) {
        // allowNativeContentPositioning: special flag for when our handle's HTML may include
        // absolutely positioned HTML child nodes.

        
        this._retrievingScrollWidth = true;

        
        if (isc.Browser.isSafari ||
            ( isc.Browser.isMoz &&
              ((handle.scrollWidth || handle.offsetWidth) <= parseInt(handle.style.width)) ) )
        {
            width = isc.Element.getScrollWidth(this.getHandle());
        } else {
            width = isc.Element.getScrollWidth(handle);
        }

        delete this._retrievingScrollWidth;

    } else if (isc.Browser._useNewSingleDivSizing && this._drewClipDiv == false) {
        
        width = handle.scrollWidth;
    } else {
        // simple content - worry only about explicitly specified ISC children, and the
        // reported scrollHeight / width

        var children = this.children,
            hasChildren = children && children.length > 0,
            handleScrollWidth = 0;

        // If we have content, look at the clip handle's reported scroll size.
        if (!hasChildren || this.allowContentAndChildren) {
            
            if (isc.Browser.isSafari ||
                (isc.Browser.isMoz && isc.Browser.version >= 21) ||
                (isc.Browser.isIE && this._drewClipDiv))
            {
                width = this.getHandle().scrollWidth;
                

                if (this.useClipDiv && !this._willSuppressOuterDivPadding(false, true)) {
                    width += isc.Element._getHPadding(this.styleName);
                }

            
            } else if (isc.Browser.isMoz) {
                var contentHandle = this.getHandle();
                var clipDivScrollWidth = handle.scrollWidth,
                    contentDivScrollWidth = contentHandle.scrollWidth;
                if (clipDivScrollWidth > contentDivScrollWidth) {
                    width = clipDivScrollWidth;
                } else {
                    var oldDisplay = contentHandle.style.display;
                    contentHandle.style.display = "inline-block";
                    width = contentHandle.scrollWidth;
                    contentHandle.style.display = oldDisplay;
                }
            } else {

                handleScrollWidth = (handle.scrollWidth || handle.offsetWidth);
                // use this scrollWidth if it's available
                if (handleScrollWidth != null && handleScrollWidth != this._$undefined) {
                    width = handleScrollWidth;

                    // Opera adds left padding and left border
                    if (isc.Browser.isOpera) {
                        
                        width -= (this.getLeftBorderSize() + this.getLeftPadding());
                    }

                    // account for explicit children at negative coords in Moz
                    
                    if (isc.Browser.isMoz && !this.isRTL()) {
                        width -= this._offscreenChildrenWidth();
                    }

                    
                    if (isc.Browser.isMoz &&
                        this.getScrollingMechanism() == isc.Canvas.NESTED_DIV)
                    {
                        var offsetAdjustment = this.getHandle().offsetLeft;
                        if (offsetAdjustment < 0) offsetAdjustment = -offsetAdjustment;
                        width -= offsetAdjustment;
                    }

                }

                
                if (isc.Browser.isSafari ||
                    (isc.Browser.isMoz && width <= parseInt(handle.style.width)))
                {
                    var contentHandle = this.getHandle(),
                        contentWidth = contentHandle.scrollWidth || contentHandle.offsetWidth;
                    if (contentWidth > width) width = contentWidth;
                    
                    /*
                    var contentHandle = this.getHandle(),
                        contentWidth = contentHandle.scrollWidth || contentHandle.offsetWidth;
                    if (contentWidth < width &&
                        (this.padding != null || (width - contentWidth) > this.getHPadding()))
                    {
                        this.logWarn("using contentWidth of: " + contentWidth +
                                     " instead of scrollWidth of: " + width +
                                     ", hPad: " + this.getHPadding());
                        width = contentWidth;
                    }
                    */
                }

            }

        } // end of check for native handle scrollWidth

        
        if (hasChildren) {
            var childrenWidth = this._getWidthSpan(this.children);
            width = Math.max(childrenWidth, width);

            // if we're enforcing scroll size, explicitly respect that
            // Note if we have content and children we should explicitly pick this up in the
            // reported scroll width of the handle - this is for the case where we have
            // children only so never measure the handle
            if (this._enforcingScrollSize != null) {
                var enforcedWidth = this._enforcingScrollSize[0];
                width = Math.max(width, enforcedWidth);
            }
        }

    }
    //if (this.containsIFrame()) {
    //    this.logWarn("Normal scrollWidth: " + width +
    //                 ", IFrame scrollWidth of: " + this._getIFrameScrollWidth());
        //return this._scrollWidth = this._getIFrameScrollWidth();
    //}

    // cache the scrollwidth to speed up future calls to this method.
    this._scrollWidth = width;
    return width;
},

// get the distance from the furthest left to the furthest right in a list of widgets

_getWidthSpan : function (children, skipHidden) {
    var mostLeft = 0, mostRight = 0,
        horizontalOverflow = this.overflow == isc.Canvas.VISIBLE ||
                             this.overflow == isc.Canvas.CLIP_H,
        mostRightChild;
    
    var overflowOnLeft = this.isRTL() && (this.overflow != isc.Canvas.VISIBLE);
    // If overflowing on left "mostRight" is always going to be the specified width
    // - anything past that is clipped.
    if (overflowOnLeft) mostRight = this.getViewportWidth();
    for (var i = 0; i < children.length; i++) {
        var child = children[i];

        
        //if (!isc.isA.Canvas(child)) continue;
        if (!child.isDrawn() && !child._hasUndrawnSize) continue;
        if (skipHidden && child.visibility == isc.Canvas.HIDDEN) continue;

        var isAbsolute = (child.position != isc.Canvas.RELATIVE),
            childWidth = child.getVisibleWidth(),
            childLeft = (isAbsolute ? child.getLeft() : child.getOffsetLeft());

        // Natively we can't scroll to view right/bottom margins of absolute elements, but we
        // can for relative/inline elements.
        // When calculating the scrollwidth of a scrollable widget, don't include the
        // right-margin of absolute children.
        // Note - we don't make this adjustment if the overflow is visible on the horizontal
        // axis as we do want the widget to expand to accommodate the child's margin on both
        // sides
        if (!horizontalOverflow && isAbsolute) childWidth -= child.getRightMargin();

        // NOTE: DO use negative coordinate to reduce rightward extent..
        if (!overflowOnLeft && (childLeft + childWidth > mostRight)) {
            mostRight = childLeft + childWidth;
            mostRightChild = child;
        }
        if (childLeft < mostLeft) mostLeft = overflowOnLeft ? childLeft : Math.max(0,childLeft);
    }
    //if (isc.isA.Window(this)) this.logWarn("most right child: " + mostRightChild);
    return mostRight - mostLeft;
},


//>	@method	canvas.getScrollHeight()    ([A])
//			Returns the scrollable height of the widget's contents, including children, ignoring
//          clipping.
//      @visibility external
//		@group	sizing
//
//		@return	(number)	height of the element that can scroll
//<
getScrollHeight : function (calculateNewValue) {
    if (isc._traceMarkers) arguments.__this = this;

    
    if (this._deferredOverflow) {
        this._deferredOverflow = null;
        this.adjustOverflow("heightCheckWhileDeferred");
        
    }
    
    if (this.containsIFrame()) {
        return this.getInnerHeight();
    }
    // If we've already cached the value, return it.
    if (!calculateNewValue && this._scrollHeight != null) return this._scrollHeight;

    var height = 0,
        handle = this.getScrollHandle();

    if (handle == null) {
        //>DEBUG
        this.logDebug("No size info available from DOM, returning user-specified size");
        //<DEBUG
        return this.getInnerHeight();
    }

    if (this.allowNativeContentPositioning) {
        
        this._retrievingScrollHeight = true;

        
        if (isc.Browser.isSafari ||
            (isc.Browser.isMoz &&
            ((handle.scrollHeight || handle.offsetHeight) <= parseInt(handle.style.height))) )
        {
             height = isc.Element.getScrollHeight(this.getHandle());
        } else {
             height = isc.Element.getScrollHeight(handle);
        }

        delete this._retrievingScrollHeight;
    } else if ((isc.Browser._useNewSingleDivSizing || isc.Browser.isIE) && this._drewClipDiv == false) {
        
        height = handle.scrollHeight;
    } else {
        // simple content - worry only about explicitly specified ISC children, and the
        // reported scrollHeight / width
        //this.logWarn("handle.scrollHeight: " + this.getHandle().scrollHeight +
        //             ", handle.offsetHeight: " + this.getHandle().offsetHeight +
        //             ", clipHandle.scrollHeight: " + this.getClipHandle().scrollHeight +
        //             ", clipHandle.offsetHeight: " + this.getClipHandle().offsetHeight);

        var hasChildren = (this.children && this.children.length > 0);
        if (!hasChildren || this.allowContentAndChildren) {
            
            if (isc.Browser._useNewSingleDivSizing || isc.Browser.isIE) {
                height = this.getHandle().scrollHeight;
                
                if (!this._willSuppressOuterDivPadding(true, false)) {
                    height += isc.Element._getVPadding(this.styleName);
                }
            } else {

                var scrollHeight = (handle.scrollHeight || handle.offsetHeight);

                // use this scrollHeight if it's available
                if (scrollHeight != null && scrollHeight != this._$undefined) {
                    height = scrollHeight;
                    if (isc.Browser.isMoz) height -= this._offscreenChildrenHeight();

                    // Opera incorrectly includes top border
                    //if (isc.Browser.isOpera) height -= this.getTopBorderSize();

                    
                    if (this._drewClipDiv &&
                        (isc.Browser.isSafari ||
                         (isc.Browser.isMoz && height <= parseInt(handle.style.height))))
                    {
                        var contentHandle = this.getHandle(),
                            contentHandleHeight = contentHandle.scrollHeight ||
                                                        contentHandle.offsetHeight;
                        if (contentHandleHeight > height) height = contentHandleHeight;
                    }
                }

            }
        }

        
        if (hasChildren) {

            var childrenHeight = this._getHeightSpan(this.children);
            //this.logWarn("handleHeight: " + height + ", childrenHeight: " + childrenHeight);
            if (childrenHeight > height) {
                height = childrenHeight;
            }

            // as with scrollWidth, if we're enforcing scroll size, explicitly respect that
            if (this._enforcingScrollSize != null) {
                var enforcedHeight = this._enforcingScrollSize[1];
                height = Math.max(height, enforcedHeight);
            }
        }
    }

    //if (this.containsIFrame()) {
    //    this.logWarn("Normal scrollHeight of: " + height +
    //                 ", IFrame scrollHeight of: " + this._getIFrameScrollHeight());
    //    //return this._scrollHeight = this._getIFrameScrollHeight();
    //}

    // cache the value to speed up future returns
    this._scrollHeight = height;
    return height;
},


_offscreenChildrenHeight : function () {
    if (!isc.isAn.Array(this.children)) return 0;
    var furthestNegative = 0;
    for (var i = 0; i < this.children.length; i++) {
        var child = this.children[i],
            childTop = (child.position == isc.Canvas.ABSOLUTE ?
                        child.getTop() : child.getOffsetTop());

        
        if (childTop < furthestNegative) furthestNegative = childTop;
    }
    //if (furthestNegative < 0) this.logWarn("offscreenHeight: " + furthestNegative);
    return -furthestNegative;
},
_offscreenChildrenWidth : function () {
    if (!isc.isAn.Array(this.children)) return 0;

    // doesn't happen for width axis with single div structure
    if (!this._drewClipDiv) return 0;

    var furthestNegative = 0;
    for (var i = 0; i < this.children.length; i++) {
        var child = this.children[i],
            childLeft = (child.position == isc.Canvas.ABSOLUTE ?
                         child.getLeft() : child.getOffsetLeft());
        if (childLeft < furthestNegative) furthestNegative = childLeft;
    }
    //if (furthestNegative < 0) this.logWarn("offscreenWidth : " + furthestNegative);
    return -furthestNegative;
},

// get the distance from the furthest up to the furthest down for a list of widgets

_getHeightSpan : function (children, skipHidden) {
    var mostUp = 0, mostDown = 0,
        verticalOverflow = this.overflow == isc.Canvas.VISIBLE ||
                           this.overflow == isc.Canvas.CLIP_H;

    for (var i = 0; i < children.length; i++) {
        var child = children[i];
        
        //if (!isc.isA.Canvas(child)) continue;

        // Always skip undrawn children - they should never impact scrollSize of a drawn parent
        // Support a flag to avoid this behavior. This is useful for LayoutSpacers which
        // are never drawn
        if (!child.isDrawn() && !child._hasUndrawnSize) continue;
        // hidden children will effect native scrollHeight so include them unless the
        // explicit skipHidden parameter was passed in (required for layouts)
        if (skipHidden && child.visibility == isc.Canvas.HIDDEN) continue;

        var isAbsolute = child.position != isc.Canvas.RELATIVE,
            childHeight = child.getVisibleHeight(),
            childTop = (isAbsolute ? child.getTop() : child.getOffsetTop());

        // Natively we can't scroll to view right/bottom margins of absolute elements, but we
        // can for relative/inline elements.
        // When calculating the scrollHeight of a scrollable widget, don't include the
        // bottom-margin of absolute children.
        // Note - we don't make this adjustment if the overflow is visible on the vertical
        // axis as we do want the widget to expand to accommodate the child's margin on both
        // sides
        if (!verticalOverflow && isAbsolute) childHeight -= child.getBottomMargin();

        // NOTE: DO use negative coordinate to reduce downward extent..
        if (childHeight + childTop > mostDown) mostDown = childHeight + childTop;
        // .. but don't report negative extents as part of span
        if (childTop < mostUp) mostUp = Math.max(0,childTop);
    }
    //this.logWarn("mostUp: " + mostUp + ", mostDown: " + mostDown);
    return mostDown - mostUp;
},

//>	@method	canvas.getScrollLeft()	(A)
// Get the number of pixels this Canvas is scrolled from its left edge.
//		@group	positioning, scrolling
//
//		@return	(number)	scrollLeft
// @visibility external
//<
getScrollLeft : function () {
    if (!this.isDrawn()) return this.scrollLeft;

    var scrollingMechanism = this.getScrollingMechanism(),
        trueScrollLeft = this.scrollLeft;
    if (scrollingMechanism == isc.Canvas.NATIVE) {
        trueScrollLeft = this.getScrollHandle().scrollLeft;
        if (this.isRTL()) {
            trueScrollLeft = this._adjustScrollLeftForRTL(trueScrollLeft);
        }
    } else if (scrollingMechanism == isc.Canvas.NESTED_DIV && this.isRTL()) {
        trueScrollLeft += this.getScrollRight() - this._scrollRight;
    }

    return trueScrollLeft;
},


//>	@method	canvas.getScrollTop()	(A)
// Get the number of pixels this Canvas is scrolled from its top edge.
//		@group	positioning, scrolling
//
//		@return	(number)	scrollTop
// @visibility external
//<
getScrollTop : function () {
    // if we're using synthetic scrolling, return our number
	if (!this.isDrawn() || this.getScrollingMechanism() != isc.Canvas.NATIVE) {
        return this.scrollTop;
    }
    // otherwise return whatever the browser reports
	return this.getScrollHandle().scrollTop;
},

// XXX setPageLeft/Top don't support percent

//> @method canvas.setPageLeft()
// Set the page-relative left coordinate of this widget.
//
// @param left (number) new left coordinate in pixels
// @group positioning
// @visibility external
//<
setPageLeft : function (left) {
    this.moveBy(left - this.getPageLeft(), 0);
},

//> @method canvas.setPageTop()
// Set the page-relative top coordinate of this widget.
//
// @param top (number) new top coordinate in pixels
// @group positioning
// @visibility external
//<
setPageTop : function (top) {
    this.moveBy(0, top - this.getPageTop());
},

// return the rect of this element's parent, or of the page if this element has no parent

getParentPageRect : function () {
	if (this.parentElement) {
        var parent = this.parentElement,
            rect = parent.getPageRect();

        // don't allow keepInParentRect widgets to go over margins
        
        var lMargin = parent.getLeftMargin(),
            tMargin = parent.getTopMargin();
        rect[0] += lMargin;
        rect[1] += tMargin;
        rect[2] -= (lMargin + parent.getRightMargin());
        rect[3] -= (tMargin + parent.getBottomMargin());

        // If the parent has borders, also disallow dragging over the borders.
        var borderSize = parent._calculateBorderSize();
        rect[0] += borderSize.left;
        rect[1] += borderSize.top;
        rect[2] -= borderSize.right + borderSize.left;
        rect[3] -= borderSize.bottom + borderSize.top;

        // if the parent has scrollbars
        var scrollBarSize=parent.getScrollbarSize();
        if (parent.vscrollOn) rect[2] -= scrollBarSize;
        if (parent.hscrollOn) rect[3] -= scrollBarSize;

        return this._adjustParentPageRect(rect);
    }
	else return [0, 0, isc.Page.getWidth(), isc.Page.getHeight()];
},

_adjustParentPageRect : function (rect) {
    if (this.peers && this.peers.length > 0) {
        // for widgets that have peers, take the degree to which all peers currently extend
        // past the master's extents, and reduce the parent space by that amount.  This is
        // required for dropShadows.
        // NOTE: This method is inexact if the peer will respond to setPageRect() on its
        // master by sticking out yet further, which seems unlikely mid-drag.
        var peerRect = this.getPeerRect(),
            thisRect = this.getPageRect();
        rect[0] += (thisRect[0] - peerRect[0]);
        rect[1] += (thisRect[1] - peerRect[1]);
        rect[2] -= (peerRect[2] - thisRect[2]);
        rect[3] -= (peerRect[3] - thisRect[3]);
    }
    return rect;
},

setPageRect : function (left, top, width, height, resizeOnly) {

    // if the first argument is an array, normalize it into workable parameters
    // (so that you can say widget.setPageRect(otherWidget.getPageRect()); )
    if (isc.isAn.Array(left)) {
        top = left[1];
        width = left[2];
        height = left[3];
        left = left[0];
    }

	// Optionally constrain size and position to the parent's rect during a dragReposition
	// or dragResize interaction.
	// We assume that:
	//  -- setPageRect is called on a keepInParentRect element either to move ~or~ to resize
	//     the element, not both simultaneously.  This is sufficient for user drags, but
    //     wouldn't work if keepInParentRect was intended to block programmatic resize.
	//  -- if either width or height is specified, this is a resize operation
	//  -- resizing occurs from one edge or corner at a time (revisit this if we support
	//     resizing around the center in the future)
	if (this.keepInParentRect && this.ns.EH.dragging && this == this.ns.EH.dragMoveTarget) {

		// are we moving or resizing the element?
		var moving = (width == null && height == null);

		// set up all of the element & parent coordinate variables

        if (width == null) width = this.getVisibleWidth();
        if (height == null) height = this.getVisibleHeight();

		var right = left + width,
			bottom = top + height,
			parentRect;

        var EH = this.ns.EH,
            dragTarget = EH.getDragTarget(EH.getLastEvent()),
            dragParent = dragTarget.parentElement;

        var explicitRect = isc.isAn.Array(this.keepInParentRect);
		if (explicitRect) {	// use provided rect (e.g. for dragOutline)
			parentRect = this.keepInParentRect;
            // offset left/top by parent's page coordinates
            if (dragParent) {
                // we may be passed component-declared values - don't side-effect them
                parentRect = parentRect.duplicate();
                parentRect[0] += dragParent.getPageLeft();
                parentRect[1] += dragParent.getPageTop();
            }
		} else {
            // use parent rect
            if (dragParent) {
                parentRect = dragTarget._adjustParentPageRect(dragTarget.getParentPageRect());
            } else {
                parentRect = this.getParentPageRect();
            }
        }

		var parentLeft = parentRect[0],
			parentTop = parentRect[1],
			parentWidth = parentRect[2],
			parentHeight = parentRect[3],
		    parentRight = parentLeft + parentWidth,
			parentBottom = parentTop + parentHeight;
        //this.logWarn("child left/top:"+ [left,top] +
        //             ", parent left/top:"+ [parentLeft,parentTop]);
        //this.logWarn("child r/b: " + [right,bottom] +
        //             ", parent r/b: " + [parentRight,parentBottom]);

        // If the parent already has scrollable content outisde the current viewport in a particular direction,
        // we should allow the child to be dragged out of the viewport in that direction (not applicable to resize)
        //
        // If the widget is keepInParentRect: true but it has no parent, get scrolling info
        // from the Page object
        if (dragParent) {
            var leftScrollExtent = dragParent.getScrollLeft(),
                rightScrollExtent = dragParent.getScrollWidth() -
                                        dragParent.getViewportWidth() - leftScrollExtent,
                topScrollExtent = dragParent.getScrollTop(),
                bottomScrollExtent = dragParent.getScrollHeight() -
                                        dragParent.getViewportHeight() - topScrollExtent;
        } else {
            var leftScrollExtent = isc.Page.getScrollLeft(),
                rightScrollExtent = isc.Page.getScrollWidth() -
                                        isc.Page.getWidth() - leftScrollExtent,
                topScrollExtent = isc.Page.getScrollTop(),
                bottomScrollExtent = isc.Page.getScrollHeight() -
                                        isc.Page.getHeight() - topScrollExtent;
        }

        if (rightScrollExtent < 0) rightScrollExtent = 0;
        if (bottomScrollExtent < 0) bottomScrollExtent = 0;

		// test the coordinates and apply constraints

		if (moving) {	// moving outside the parent rect?

			if (left < parentLeft - leftScrollExtent) {
				left = parentLeft - leftScrollExtent;
			}
			else if (right > parentRight + rightScrollExtent) {
				left = parentRight + rightScrollExtent - width;
			}

			if (top < parentTop - topScrollExtent) {
				top = parentTop - topScrollExtent;
			}
			else if (bottom > parentBottom + bottomScrollExtent) {
				top = parentBottom + bottomScrollExtent - height;
			}

		} else {	// resizing outside the parent rect?

			if (left < parentLeft) {
				width = width - (parentLeft - left);
				left = parentLeft;
			} else if (right > parentRight) {
				width = width - (right - parentRight);
			}

			if (top < parentTop) {
				height = height - (parentTop - top);
				top = parentTop;
			} else if (bottom > parentBottom) {
				height = height - (bottom - parentBottom);
			}

		}

	}
    // end keepInParentRect

    
    this.moveBy(left - this.getPageLeft(), top - this.getPageTop());

    if (resizeOnly) {
        
        var oldWidth = this.getVisibleWidth(),
            oldHeight = this.getVisibleHeight(),
            desiredDeltaX = oldWidth - width,
            desiredDeltaY = oldHeight - height;

        this.resizeTo(width,height);
        this.redrawIfDirty("setPageRect"); // to get valid new size

        var actualDeltaX = (oldWidth - this.getVisibleWidth()),
            actualDeltaY = (oldHeight - this.getVisibleHeight());

        if (left > this.getPageLeft()) left -= (desiredDeltaX - actualDeltaX);
        if (top > this.getPageTop()) top -= (desiredDeltaY - actualDeltaY);
    } else {
        this.resizeTo(width,height);
    }

},

// getCanvasLeft() and getCanvasTop()
// returns a canvas's left and top offset relative to their ISC parentElement canvas.

//>	@method	canvas.getCanvasLeft()
//		Return the absolute left coordinate of this object relative to the top/left of it's
//      SmartClient parent element, in pixels.  Value returned is distance from outside of
//      this widget's border/margins (if any) to inside of parent's handle.
//
// @param [ancestor] (Canvas) Ancestor canvas to check against. If not passed, always checks against
//  this canvas' direct parent.
//		@group	positioning
//
//		@return	(number)	SmartClient canvas left coordinate
//<
getCanvasLeft : function (ancestor) {
    if (ancestor != null) {
        if (!ancestor.contains(this, false)) {
            this.logWarn("getCanvasTop passed ancestor:" + ancestor +
                ". This is not an ancestor of this component - ignoring");
            ancestor = this.parentElement;
        }
    } else {
        ancestor = this.parentElement;
    }

    // See "Widget Positioning and Sizing Methods" comment for a discussion of coordinate systems
    // in DOM and ISC

    // If we haven't been drawn yet, return the specified coordinate
    if (!this.isDrawn() ||
        
        this._isDisplayNone())
    {
        if (!this.isDrawn() && this.position == isc.Canvas.RELATIVE) {
            //>DEBUG technically, an absolutely positioned widget would also have this problem
            // if placed within an element that served as an offsetParent (eg, an absolutely
            // positioned DIV), but that scenario is very unlikely and if we catch it then this
            // warning will fire for the common case of manipulating the coordinates of a
            // top-level absolutely positioned widget before drawing it.
            this.logWarn("getCanvasLeft(): Called on undrawn relatively-position widget '" +
                         this.getID() + "'.  The drawn coordinates can not be reliably " +
                         "calculated until the widget has drawn - returning estimated position");
            //<DEBUG
        }

        var left = this.left,
            pe = this.parentElement;
        while (ancestor != pe) {
            left += pe.left;
            pe = pe.parentElement;
        }

        return left;
    }

    // fall through to getLeftOffset (passing in the parent canvas if there is one)
    var returnVal = this.getLeftOffset(ancestor);

    

    return returnVal;
},

//> @method canvas.getPageOffsets() [A]
// Returns the page-relative left and top coordinates of the widget on the page.
// @return (ElementOffsets)
//<
getPageOffsets : function () {
    if (isc._traceMarkers) arguments.__this = this;

    var handle = this.getClipHandle();

    
    if (handle && this._isDisplayNone()) {
        handle = null;
    }

    if (handle == null) {
        // If we haven't been drawn the coordinates may be wrong for a number of reasons - log
        // a warning
        if (!this.isDrawn() && this.position == isc.Canvas.RELATIVE) {
            //>DEBUG technically, an absolutely positioned widget would also have this problem
            // if placed within an element that served as an offsetParent (eg, an absolutely
            // positioned DIV), but that scenario is very unlikely and if we catch it then this
            // warning will fire for the common case of manipulating the coordinates of a
            // top-level absolutely positioned widget before drawing it.
            this.logWarn("getPageOffsets(): Called on undrawn relatively-position widget '" +
                         this.getID() + "'.  The page level coordinates can not be reliably " +
                         "calculated until the widget has been drawn - returning estimated position");
            //<DEBUG
        }

        var parent = this.parentElement;

        var left, top;
        if (parent) {
            var scrollDelta = 0;
            if (parent.hscrollOn) {
                if (!this.isRTL()) scrollDelta = parent.getScrollLeft();
                else {
                    var maxScroll = parent.getScrollWidth() - parent.getViewportWidth();
                    scrollDelta = -1 * (maxScroll - parent.getScrollLeft());
                }
            }

            var parentPageOffsets = parent.getPageOffsets();

            left = this.getOffsetLeft() + parent.getLeftBorderSize() + parent.getLeftMargin() +
                   parentPageOffsets.left - scrollDelta;

            // parent.getPageTop gives us page coords from outside border/margin of
            // parent - offsetLeft/offsetTop gives us the value to the inside of the parent,
            // so we need to add the parent's border/margin
            top = this.getOffsetTop() + parent.getTopBorderSize() + parent.getTopMargin() +
                  parentPageOffsets.top - parent.getScrollTop();
        } else {
            left = this.getOffsetLeft();
            top = this.getOffsetTop();
        }
        // In RTL we stick scrollbars on our left and shift the handle the right to accomodate
        // them, but getPageRect et all is expected to be the distance to the outside of
        // scrollbars so that's what we'll use
        if (this.isRTL() && this.vscrollOn && this.showCustomScrollbars) left -= this.getScrollbarSize();

        return {
            left: left,
            top: top
        };
    }

    

    if (this.useClientRectAPI && handle.getBoundingClientRect != null) {
        var bcr = handle.getBoundingClientRect();

        var left = bcr.left;

        left -= this.getLeftMargin();

        
        var adjustForScroll = !isc.Browser.isIE9;

        if (adjustForScroll) {
            
            var pageScrollOffset = isc.Page.getScrollLeft(true);
            left += pageScrollOffset;
        }

        if (this.isRTL()) {
            if (this.vscrollOn && this.showCustomScrollbars) {
                left -= this.getScrollbarSize();
            }

            if (isc.Browser.isIE && (isc.Browser.version < 9 || !isc.Browser.isStrict) &&
                (isc.Page.getBodyOverflow() != isc.Canvas.HIDDEN))
            {
                if (isc.EH._pageScrollbarThickness == null) {
                    isc.EH._pageScrollbarThickness = (document.body.offsetWidth -
                                                      document.body.clientWidth);
                }
                left -= isc.EH._pageScrollbarThickness;
            }
        }

        var top = bcr.top;
        // boundingClientRect returns position inside margins, and coords are relative to
        // viewport rather than page
        top -= this.getTopMargin();
        top += isc.Page.getScrollTop();

        return {
            left: left,
            top: top
        };
    }



    // If we are drawn use getOffsets().
    
    var offsets = this.getOffsets(),
        margins = this._calculateMargins();
    return {
        left: offsets.left - margins.left,
        top: offsets.top - margins.top
    };
},

//>	@method	canvas.getPageLeft()    ([A])
// Returns the page-relative left coordinate of the widget on the page, in pixels.
//		@visibility external
//		@group	positioning
//		@return	(number)	global left coordinate
//<
getPageLeft : function () {
    return this.getPageOffsets().left;
},

useClientRectAPI:false,
useBoxObjectAPI:false,
useBoxObjectAPISelectively:true,


//> @method canvas.getOffsets() [A]
// @param (DOMElement or Canvas) targetElement
// @return (ElementOffsets)
//<
getOffsets : function (targetElement) {
    var offsets = this.ns.Element.getOffsets(this, targetElement, this.isRTL(), true);
    if (this.isRTL() && this.vscrollOn && this.showCustomScrollbars) offsets.left -= this.getScrollbarSize();

    

    return offsets;
},

// getLeftOffset(targetElement)
//
// DOM Only method to return our absolute position within a DOM parent element
// If no target parent element is passed, we return page level position.
//
getLeftOffset : function (targetElement) {

    var left = this.ns.Element.getOffset(isc.Canvas.LEFT, this, targetElement, this.isRTL(), true);
    if (this.isRTL() && this.vscrollOn && this.showCustomScrollbars) left -= this.getScrollbarSize();

    

    return left;
},


//>	@method	canvas.getCanvasTop()
//		Return the absolute top coordinate of this object relative to it's SmartClient parent
//      element (viewport top), in pixels.  Value returned is distance from outside of this
//      widget's border/margins (if any) to inside of parent's handle (top of content, not page
//      coordinate of widget, so does not change when the widget is scrolled).
//
// @param [ancestor] (Canvas) Ancestor canvas to check against. If not passed, always checks against
//  this canvas' direct parent.
//		@group	positioning
//
//		@return	(number)	SmartClient canvas top coordinate
//<
getCanvasTop : function (ancestor) {
    if (ancestor != null) {
        if (!ancestor.contains(this, false)) {
            this.logWarn("getCanvasTop passed ancestor:" + ancestor +
                ". This is not an ancestor of this component - ignoring");
            ancestor = this.parentElement;
        }
    } else {
        ancestor = this.parentElement;
    }

    // See "Widget Positioning and Sizing Methods" comment for a discussion of coordinate systems
    // in DOM and ISC

    if (!this.isDrawn() ||
        
        this._isDisplayNone())
    {
        //>DEBUG
        if (!this.isDrawn() && this.position == isc.Canvas.RELATIVE) {
            this.logWarn("getCanvasTop(): Called on undrawn relatively-position widget '" +
                         this.getID() + "'.  The drawn coordinates can not be reliably " +
                         "calculated until the widget has drawn - returning estimated position");
        } //<DEBUG

        var top = this.top,
            pe = this.parentElement;
        while (ancestor != pe) {
            top += pe.top;
            pe = pe.parentElement;
        }

        return top;
    }

    // fall through to getTopOffset (passing in the parent canvas if there is one)
    var returnVal = this.getTopOffset(ancestor);

    

    return returnVal;
},

//>	@method	canvas.getPageTop() ([A])
// Returns the page-relative top coordinate of the widget on the page, in pixels
//      @visibility external
//		@group	positioning
//		@return	(number)	GLOBAL top coordinate
//<
getPageTop : function () {
    return this.getPageOffsets().top;
},

// Return our absolute position within a DOM parent element.
// If no target parent element is passed, we return page level position.
getTopOffset : function (targetElement) {

    var top = this.ns.Element.getOffset(isc.Canvas.TOP, this, targetElement, null, true);

    

    return top;

},

//>	@method	canvas.getPageRight()
// Return the page-relative right coordinate of this object, in pixels.
//
//		@group	positioning
//
//		@return	(number)	GLOBAL right coordinate
// @visibility external
//<
getPageRight : function (pageOffsets) {
    pageOffsets = pageOffsets || this.getPageOffsets();
    return pageOffsets.left + this.getVisibleWidth();
},


//>	@method	canvas.getPageBottom()
// Return the page-relative bottom coordinate of this object, in pixels.
//		@group	positioning
//
//		@return	(number)	GLOBAL bottom coordinate
// @visibility external
//<
getPageBottom : function (pageOffsets) {
    pageOffsets = pageOffsets || this.getPageOffsets();
    return pageOffsets.top + this.getVisibleHeight();
},


getPageRect : function () {
    var pageOffsets = this.getPageOffsets();
    return [pageOffsets.left, pageOffsets.top,
            this.getVisibleWidth(), this.getVisibleHeight()];
},

// Scrolling Mechanisms
// --------------------------------------------------------------------------------------------

//>	@method	canvas.usingCSSScrollbars()	(A)
// Return whether or not we are configured to show native CSS scrollbars when
// scrollWidth/Height exceeds viewport width/height.
//		@group	scrolling
//
//		@return	(boolean)
//<
usingCSSScrollbars : function () {
	return  ! this.showCustomScrollbars &&
        (this.overflow == isc.Canvas.AUTO || this.overflow == isc.Canvas.SCROLL);
},

//>	@method	canvas.getScrollingMechanism()	(A)
//			Return how we're implementing scrolling - one of 3 possibilities:
//           - "native" = assigning directly to handle.scrollLeft / scrollTop
//           - "clip" = using a clip region to simulate scrolling
//           - "nestedDiv" = moving an inner div within an outer clipDiv
//		@group	scrolling
//
//		@return	(enum)  one of "native", "clip", "nestedDiv"
//<
// Note: If we are showing css scrollbars (this.showNativeScrollbars is true, and this.overflow
// is auto or scroll), scrollingMechanism is always native.
// Otherwise it varies by platform (due to limitations in the various platforms)
getScrollingMechanism : function () {

    
    if (!this._scrollingMechanism) {
        
        if (!this.showCustomScrollbars &&
            (this.overflow == isc.Canvas.AUTO || this.overflow == isc.Canvas.SCROLL))
        {
            this._scrollingMechanism = isc.Canvas.NATIVE;
        } else {
            // We're either showing custom scrollbars or not showing scrollbars at all for this
            // widget

            
            
            if (isc.Browser.isIE && this.isRTL() &&
                (isc.Browser.version <= 7 || (isc.Browser.version == 8 && !isc.Browser.isStrict)))
            {
                this._scrollingMechanism = isc.Canvas.NESTED_DIV;

            // In every other case we can assign directly to handle.scrollLeft / scrollTop
            } else {
                this._scrollingMechanism = isc.Canvas.NATIVE;
            }
        }
    }

    return this._scrollingMechanism;
},


// Border, Padding and Margin
// --------------------------------------------------------------------------------------------
//  Border, Margin and Padding properties can all be specified for widgets at the widget level, or
//  via the css class applied to them (through their className property)
//  We provide methods to get at the thickness of these properties for each widget - whether the
//  property is defined on the widget directly, or through it's css class.



//>	@method canvas.setMargin()
// Set the CSS Margin, in pixels, for this component.  Margin provides blank space outside of
// the border.
// <P>
// This property sets the same thickness of margin on every side.  Differing per-side
// margins can be set in a CSS style and applied via +link{styleName}.
// <P>
// Note that the specified size of the widget will be the size <b>including</b> the margin
// thickness on each side.
//
// @param margin (number) new margin in pixels
//
// @visibility external
//<
setMargin : function (margin) {
    

    this._cachedMargins = null;
    this._fullMargins = null;

    if (margin == null) {
        delete this.margin
    } else {
        var origMargin = margin;
        if (isc.isA.String(margin)) margin = parseInt(margin);
        if (!isc.isA.Number(margin)) {
            this.logWarn("setMargin() passed invalid margin:"+ origMargin + ", ignoring.");
            return;
        }
        this.margin = margin;
    }
    var styleHandle = this.getStyleHandle();
    if (!styleHandle) return;

    this._applyFullMargins();

    // adjustOverflow - since this will change our handle-size
    
    this.adjustOverflow("setMargin");

    
    this.innerSizeChanged("Margin thickness changed");

},

// Update the margins applied to a widget after draw.
_applyFullMargins : function () {
    var handle = this.getClipHandle();
    if (!handle) return;

    // optimization: if we have nothing that would introduce automatic per-side margin
    // settings..
    if (!this._edgesAsPeer() && this._attachedPeerMap == null) {
        handle.style.marginTop = "";
        handle.style.marginBottom = "";
        handle.style.marginLeft = "";
        handle.style.marginRight = "";
        if (this.margin == null) handle.style.margin = 0;
        else handle.style.margin = this.margin + isc.px;
        return;
    }

    // Support assymetric margins if necessary.
    var margins = this._calculateMargins();
    handle.style.marginTop = margins.top + isc.px;
    handle.style.marginLeft = margins.left + isc.px;
    handle.style.marginBottom = margins.bottom + isc.px;
    handle.style.marginRight = margins.right + isc.px;
},

//>	@method canvas.getMargin()
//			Returns the explicitly specified margin for this widget (set up via this.setMargin())
//		@group	appearance
//		@return	(string)    margin property for this widget
//<
getMargin : function () {
    return this.margin;
},


//>	@method canvas.getTopMargin()
//			Return the size of the top margin for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no top margin
//<
getTopMargin : function () {
    return this._calculateMargins().top;
},


//>	@method canvas.getLeftMargin()
//			Return the size of the left margin for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no left margin
//<
getLeftMargin : function () {
    return this._calculateMargins().left;
},

//>	@method canvas.getBottomMargin()
//			Return the size of the bottom margin for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no bottom margin
//<
getBottomMargin : function () {
    return this._calculateMargins().bottom;
},


//>	@method canvas.getRightMargin()
//			Return the size of the right margin for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no right margin
//<
getRightMargin : function () {
    return this._calculateMargins().right;
},

//>	@method canvas._calculateMargins()
//      Determines the size of the margins for this widget (on each side), by looking at the
//      widget's "Margin" property, it's handle, and it's CSS class.
//      Uses caching for speed
//
//		@group	appearance
//      @return (object)    Object with properties 'left', 'top', 'bottom', 'right', specifying the
//                          width in pixels of the margin on each side of this widget.
//<


_removeDestroyedPeers : function (list, side) {
    var destroyed = [];
    for (var i = 0; i < list.length; i++) {
        if (list[i].destroyed) {
            destroyed[destroyed.length] = {peer:list[i], side:side};
            list[i] = null;
        }
    }
    list.removeEmpty();
    return destroyed;
},
_calculateMargins : function () {
    
    var attachedPeers = this._attachedPeerMap,
        hasAPs = (attachedPeers != null),
        topPeers,leftPeers,rightPeers,bottomPeers;
    if (hasAPs) {
        topPeers = attachedPeers.top;
        bottomPeers = attachedPeers.bottom;
        leftPeers = attachedPeers.left;
        rightPeers = attachedPeers.right;

        var destroyedPeers = [];
        if (topPeers != null) destroyedPeers.addList(this._removeDestroyedPeers(topPeers, "top"));
        if (bottomPeers != null) destroyedPeers.addList(this._removeDestroyedPeers(bottomPeers, "bottom"));
        if (leftPeers != null) destroyedPeers.addList(this._removeDestroyedPeers(leftPeers, "left"));
        if (rightPeers != null) rightPeers.addList(this._removeDestroyedPeers(rightPeers, "right"));
        if (destroyedPeers.length > 0) {
            for (var i = 0 ; i < destroyedPeers.length; i++) {
                this._unRegisterAttachedPeer(destroyedPeers[i].peer, destroyedPeers[i].side);
            }
        }

        if ((topPeers == null || topPeers.length == 0) &&
            (bottomPeers == null || bottomPeers.length == 0) &&
            (leftPeers == null || leftPeers.length == 0) &&
            (rightPeers == null || rightPeers.length == 0)) hasAPs = false;
    }
    if (!this._edgesAsPeer() && !hasAPs) return this._calculateNormalMargins();
    

    var fullMargins = this._fullMargins;
    if (fullMargins) return fullMargins;

    var margins = this._getSpecifiedMargins();
    fullMargins = {
        left:margins.left,
        right:margins.right,
        top:margins.top,
        bottom:margins.bottom
    };

    if (hasAPs) {
        
        if (topPeers) {
            for (var i = 0; i < topPeers.length; i++) {
                var topPeer = topPeers[i];
                fullMargins.top += topPeer.getVisibleHeight();
                if (topPeer._attachedPeerOffset != null) {
                    fullMargins.top -= topPeer._attachedPeerOffset;
                }
            }
        }
        if (bottomPeers) {
            for (var i = 0; i < bottomPeers.length; i++) {
                var bottomPeer = bottomPeers[i];
                fullMargins.bottom += bottomPeer.getVisibleHeight();
                if (bottomPeer._attachedPeerOffset != null) {
                    fullMargins.bottom -= bottomPeer._attachedPeerOffset;
                }
            }
        }
        if (leftPeers) {
            for (var i = 0; i < leftPeers.length; i++) {
                var leftPeer = leftPeers[i];
                fullMargins.left += leftPeer.getVisibleWidth();
                if (leftPeer._attachedPeerOffset != null) {
                    fullMargins.left -= leftPeer._attachedPeerOffset;
                }
            }
        }
        if (rightPeers) {
            for (var i = 0; i < rightPeers.length; i++) {
                var rightPeer = rightPeers[i];
                fullMargins.right += rightPeer.getVisibleWidth();
                if (rightPeer._attachedPeerOffset != null) {
                    fullMargins.right -= rightPeer._attachedPeerOffset;
                }
            }
        }
    }

    //>RoundCorners add to margins to leave room for surrounding EdgedCanvas
    
    if (this._edgesAsPeer()) {
        var edge = this._createEdges();
        // add to margins to allow room for the edgedCanvas
        fullMargins.left += edge._leftMargin,
        fullMargins.right += edge._rightMargin,
        fullMargins.top += edge._topMargin,
        fullMargins.bottom += edge._bottomMargin
    }
    //<RoundCorners

    
    return (this._fullMargins = fullMargins);
},

_getSpecifiedMargins : function () {
    var drawn = this._drawn;
    this._drawn = false;
    var margins = this._calculateNormalMargins();
    this._drawn = drawn;
    return margins;
},

_calculateNormalMargins : function () {

    // If we've already calculated it, return the cached version for speed
    // (Cleared out by 'setMargin()')
    if (this._cachedMargins != null) return this._cachedMargins;

    // First check for this.margin / directly applying the margin to the DOM.
    // We'll then check the css class for this widget for any margins we don't find applied directly

    // There are various options for setting the css margin width -
    //  Measure:    float followed by units designator (cm, mm, in, pt, pc, px, OR em, or ex)
    //  Percentage: (fairly self explanatory!)
    //
    // We currently only handle returning widths specified in pixels.
    var margins = {},
        pxString = isc.px;

    // If it's not drawn - Look at this.margin
    if (!this.isDrawn()) {

        // We are assuming here that the margin will be uniform on all sides - something like
        // "1px"
        var marginString = this.margin;

        if (isc.isA.String(marginString)) {
            // We should handle either "2" or "2px" format margin property
            // (This will also handle "2px 2px 2px 2px", but not asymmetric margins applied in this
            //  way)
            if (isc.endsWith(marginString, pxString) || parseInt(marginString) + isc.emptyString == marginString)
                marginString = parseInt(marginString);
        }

        // This will handle the case where a margin was specified as a number directly, or where
        // we've parsed a string
        if (isc.isA.Number(marginString)) {
            margins.top = marginString;
            margins.bottom = marginString;
            margins.left = marginString;
            margins.right = marginString;

            // cache and return it, we're done
            this._cachedMargins = margins;
            return margins;
        }

    // If it is drawn, check the DOM for the margin actually applied to the div
    } else {
        

        var handleStyle = this.getStyleHandle(),
            marginLeft = handleStyle.marginLeft,
            marginRight = handleStyle.marginRight,
            marginTop = handleStyle.marginTop,
            marginBottom = handleStyle.marginBottom;

        if (isc.isA.String(marginLeft) && isc.endsWith(marginLeft, pxString))
            marginLeft = parseInt(marginLeft);

        if (isc.isA.String(marginRight) && isc.endsWith(marginRight, pxString))
            marginRight = parseInt(marginRight)

        if (isc.isA.String(marginTop) && isc.endsWith(marginTop, pxString))
            marginTop = parseInt(marginTop);

        if (isc.isA.String(marginBottom) && isc.endsWith(marginBottom, pxString))
            marginBottom = parseInt(marginBottom)

        if (isc.isA.Number(marginLeft)) margins.left = marginLeft;
        if (isc.isA.Number(marginRight)) margins.right = marginRight;
        if (isc.isA.Number(marginTop)) margins.top = marginTop;
        if (isc.isA.Number(marginBottom)) margins.bottom = marginBottom;
    }

    // Having looked at the handle (or 'margin' property for undrawn widgets), if we have not
    // determined margin sizes for any side, the widget will display any margin specified on the
    // css class applied to it.
    // Check the styleObject from the className for any margin's we haven't already determined.
    if (this.className) {

        if (!isc.isA.Number(margins.left))
            margins.left = isc.Element._getLeftMargin(this.className);
        if (!isc.isA.Number(margins.right))
            margins.right = isc.Element._getRightMargin(this.className);
        if (!isc.isA.Number(margins.top))
            margins.top = isc.Element._getTopMargin(this.className);
        if (!isc.isA.Number(margins.bottom))
            margins.bottom = isc.Element._getBottomMargin(this.className);
    } else {
        // widget has no margin on any sides we haven't got yet!
        if (!isc.isA.Number(margins.left))
            margins.left = 0;
        if (!isc.isA.Number(margins.right))
            margins.right = 0;
        if (!isc.isA.Number(margins.top))
            margins.top = 0;
        if (!isc.isA.Number(margins.bottom))
            margins.bottom = 0;
    }

    
    return (this._cachedMargins = margins);
},


//>	@method canvas.getTopBorderSize()
//			Return the size of the top border for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no border
//<
getTopBorderSize : function () {
    return this._calculateBorderSize().top;
},

//>	@method canvas.getBottomBorderSize()
//			Return the size of the bottom border for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no border
//<
getBottomBorderSize : function () {
    return this._calculateBorderSize().bottom;
},

//>	@method canvas.getLeftBorderSize()
//			Return the size of the left border for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no border
//<
getLeftBorderSize : function () {
    return this._calculateBorderSize().left;
},

//>	@method canvas.getRightBorderSize()
//			Return the size of the right border for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no border
//<
getRightBorderSize : function () {
    return this._calculateBorderSize().right;
},


//>	@method canvas.getHBorderSize()
//			Return the size of the horizontal borders (left and right) for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no border
//<
getHBorderSize : function () {
    return (this.getLeftBorderSize() + this.getRightBorderSize());
},


//>	@method canvas.getVBorderSize()
//			Return the total size of the vertical borders (top and bottom) for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no border
//<
getVBorderSize : function () {
    return this.getTopBorderSize() + this.getBottomBorderSize();
},

//>	@method canvas._calculateBorderSize()
//      Determines the size of the border for this widget (on each side), from the 'border' property
//      for the widget, and any specified css class.
//      Stores resulting values in _cachedBorderSize() property.
//
//		@group	appearance
//      @return (object)    Object with properties 'left', 'top', 'bottom', 'right', specifying the
//                          width in pixels of the border on each side of this widget.
//<
_calculateBorderSize : function () {

    // If we've already calculated it, return the cached version for speed
    // (Cleared out by 'setBorder()')
    if (this._cachedBorderSize != null) return this._cachedBorderSize;

    // Determine the borderSize from the DOM.
    var borderSizes = {},
        pxString = isc.px;

    // The Border for a widget can be applied directly to its handle's style attribute - done
    // via the "border" property of the widget, or (if that is not defined), it is picked up
    // from the CSS class for the widget.
    // In this method we will check for an explicitly specified border for the widget, and if none
    // is found, fall through to checking the border on the widget's css class.
    // - Note on the 'border' property.
    //   widget.border is applied directly to the clipHandle's style. It should be of the form
    //   '2px solid black' (so a string of CSS designating a border style).
    //   We don't support the developer applying different borders to different sides, except via
    //   a css class applied to the widget.
    

    // There are various options for setting the css border width -
    //  String:     medium, thin, thick
    //  Measure:    float followed by units designator (cm, mm, in, pt, pc, px, OR em, or ex)
    //
    // We only support sizes specified in px.
    

    // Border applied to the handle directly
    //
    // If it's not drawn - Look at border specified via this.border
    if (!this.isDrawn()) {

        // We are assuming here that the border will be uniform on all sides - something like
        // "1px solid black"
        var borderString = this.border;

        // If we can't find a width in pixels, assume width is not defined in this.border
        // We check for this via the presence of the 'px' string. Note that
        // css supports specifying the border style in any order, so we need to use a regexp to
        // find the right part of the string (next to the 'px').
        
        if (borderString != null && isc.contains(borderString, pxString)) {
            var borderSize = borderString.match(/\s*\d+px/g);

            // All the borders should be the same size - use the first size encountered
            if (isc.isAn.Array(borderSize)) borderSize = parseInt(borderSize[0]);
            else borderSize = parseInt(borderSize);

            if (isc.isA.Number(borderSize)) {
                this._cachedBorderSize = {
                    left:borderSize,
                    right:borderSize,
                    top:borderSize,
                    bottom:borderSize
                }
                return this._cachedBorderSize;
            }
        }

    // If it is drawn, check the DOM for the border actually applied to the div
    } else {

        // examine the style of the drawn HTML element (before looking at the css class)
        

        var handleStyle = this.getStyleHandle(),
            borderLeft = handleStyle.borderLeftWidth,
            borderRight = handleStyle.borderRightWidth,
            borderTop = handleStyle.borderTopWidth,
            borderBottom = handleStyle.borderBottomWidth;

        if (isc.isA.String(borderLeft) && isc.endsWith(borderLeft, pxString))
            borderLeft = parseInt(borderLeft);

        if (isc.isA.String(borderRight) && isc.endsWith(borderRight, pxString))
            borderRight = parseInt(borderRight)

        if (isc.isA.String(borderTop) && isc.endsWith(borderTop, pxString))
            borderTop = parseInt(borderTop);

        if (isc.isA.String(borderBottom) && isc.endsWith(borderBottom, pxString))
            borderBottom = parseInt(borderBottom)
        
        if (isc.isA.Number(borderLeft)) borderSizes.left = borderLeft;
        if (isc.isA.Number(borderRight)) borderSizes.right = borderRight;
        if (isc.isA.Number(borderTop)) borderSizes.top = borderTop;
        if (isc.isA.Number(borderBottom)) borderSizes.bottom = borderBottom;

    }

    // Having looked at the handle (or 'border' property for undrawn widgets), if we have not
    // determined sizes for any side, derive the border sizes from the css class applied to the
    // widget.
    var className = this._getBorderClassName();
    if (className) {
        // Determine the borderWidth from the css style class for this element
        if (!isc.isA.Number(borderSizes.left))
            borderSizes.left = isc.Element._getLeftBorderSize(className);
        if (!isc.isA.Number(borderSizes.right))
            borderSizes.right = isc.Element._getRightBorderSize(className);
        if (!isc.isA.Number(borderSizes.top))
            borderSizes.top = isc.Element._getTopBorderSize(className);
        if (!isc.isA.Number(borderSizes.bottom))
            borderSizes.bottom = isc.Element._getBottomBorderSize(className);
    } else {
        // widget has no border on any sides we haven't got yet!
        if (!isc.isA.Number(borderSizes.left))
            borderSizes.left = 0;
        if (!isc.isA.Number(borderSizes.right))
            borderSizes.right = 0;
        if (!isc.isA.Number(borderSizes.top))
            borderSizes.top = 0;
        if (!isc.isA.Number(borderSizes.bottom))
            borderSizes.bottom = 0;
    }

    
    return (this._cachedBorderSize = borderSizes);
},

// CSS class that actually governs what borders appear on the handle.
// This is overridden in Button.js where we apply the baseStyle + modifier to the
// handle directly.
_getBorderClassName : function () {
    return this.className;
},

// Unexposed method to set explicit per-side padding
// Padding is applied to the content handle. If we drew a clip div, then we need to access the
// content handle's CSSStyleDeclaration. Otherwise, the clip div is the content div, so we can
// use the cached style handle.
setTopPadding : function (padding) {
    this._cachedPadding = null;
    this.topPadding = padding;
    if (isc.isA.Number(padding)) padding += "px";
    if (this.isDrawn()) {
        
        var styleHandle = this._drewClipDiv ? this.getHandle().style : this.getStyleHandle();
        styleHandle.paddingTop = padding;
    }
},
setLeftPadding : function (padding) {
    this._cachedPadding = null;
    this.leftPadding = padding;
    if (isc.isA.Number(padding)) padding += "px";
    if (this.isDrawn()) {
        
        var styleHandle = this._drewClipDiv ? this.getHandle().style : this.getStyleHandle();
        styleHandle.paddingLeft = padding;
    }
},
setRightPadding : function (padding) {
    this._cachedPadding = null;
    this.rightPadding = padding;
    if (isc.isA.Number(padding)) padding += "px";
    if (this.isDrawn()) {
        
        var styleHandle = this._drewClipDiv ? this.getHandle().style : this.getStyleHandle();
        styleHandle.paddingRight = padding;
    }
},
setBottomPadding : function (padding) {
    this._cachedPadding = null;
    this.bottomPadding = padding;
    if (isc.isA.Number(padding)) padding += "px";
    if (this.isDrawn()) {
        
        var styleHandle = this._drewClipDiv ? this.getHandle().style : this.getStyleHandle();
        styleHandle.paddingBottom = padding;
    }
},

//>	@method canvas.setPadding()
// Set the CSS padding of this component, in pixels.  Padding provides space between the border
// and the component's contents.
// <P>
// This property sets the same thickness of padding on every side.  Differing per-side
// padding can be set in a CSS style and applied via +link{styleName}.
// <P>
// @group appearance
// @param newPadding (number) new padding in pixels
// @visibility external
//<
_$0px:"0px",
setPadding : function (padding) {
    this._cachedPadding = null;

    if (padding != null) {
        var origPadding = padding;
        if (isc.isA.String(padding)) padding = parseInt(padding);
        if (!isc.isA.Number(padding)) {
            this.logWarn("setPadding passed unrecognized value:"+ origPadding + " - ignoring");
            return;
        }
    }
    this.padding = padding;

    // No support in non DOM browsers really
    var handle = isc.Browser.isDOM ? this.getHandle() : null;
    if (!handle) {
        return;
    }

    // if padding is null - clear out this.padding
    if (padding == null) {

        // clear out the padding from the handle
        // if we're using clipDivs, also clear out any padding from the clipDiv, since we'll
        // want the css class's padding (if there is any) to be applied.
        handle.style.padding = null;
        if (this._drewClipDiv) this.getClipHandle().style.padding = null;

    } else {
        // update the handle
        // Note - if we're using clip divs, ensure that the clip div's padding is explicitly
        // set to zero so we don't get nested padding from the specified padding property and
        // the className applied to the element
        handle.style.padding = this.padding + isc.px;
        if (this._drewClipDiv) this.getClipHandle().style.padding = this._$0px;
    }
    
},

//>	@method canvas.getPadding()
//			Return the size of the padding around this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no padding
//<
getPadding : function () {
    return this.padding;
},

//>	@method canvas.getTopPadding()
//			Return the size of the top padding above this canvas' content.
//          Derives value from explicitly specified 'padding' if present, or from css class.
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no padding
//<
getTopPadding : function () {
    return this._calculatePadding().top;
},

//>	@method canvas.getBottomPadding()
//			Return the size of the bottom padding (below this canvas' content).
//          Derives value from explicitly specified 'padding' if present, or from css class.
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no padding
//<
getBottomPadding : function () {
    return this._calculatePadding().bottom;
},

//>	@method canvas.getLeftPadding()
//			Return the size of the left padding for this canvas.
//          Derives value from explicitly specified 'padding' if present, or from css class.
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no padding
//<
getLeftPadding : function () {
    return this._calculatePadding().left;
},

//>	@method canvas.getRightPadding()
//			Return the size of the right padding for this canvas.
//          Derives value from explicitly specified 'padding' if present, or from css class.
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no padding
//<
getRightPadding : function () {
    return this._calculatePadding().right;
},

//>	@method canvas.getVPadding()
//			Return the vertical size of the padding around this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no padding
//<
getVPadding : function () {
    return this.getTopPadding() + this.getBottomPadding();
},


//>	@method canvas.getHPadding()
//			Return the horizontal size of the padding around this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no padding
//<
getHPadding : function () {
    return this.getLeftPadding() + this.getRightPadding();
},

//>	@method canvas._calculatePadding()
//			Determine the size of the padding on each side of this canvas.
//          Derives value from explicitly specified 'padding' if present, or from css class.
//          Stores in this._cachedPadding object.
//		@group	appearance
//<
_calculatePadding : function () {

    // If we've already calculated padding for this widget, return the cached version for speed
    // (cleared out by 'setPadding()')
    if (this._cachedPadding != null) return this._cachedPadding;
    // Determine the padding size from the DOM.

    var padding = {},
        pxString = isc.px;

    // if it's drawn examine the style of the drawn HTML element first
    if (this.isDrawn() && this.getHandle() != null) {
        // Note: if we're writing out two DIVS (as with Moz), the padding will be
        // applied to the inner contentDiv, rather than the outer clipDiv
        // We must use getHandle().style rather than getStyleHandle() therefore as
        // getStyleHandle() will examine the style applied to the clipDiv.

        var handleStyle = this.getHandle().style;

        if (handleStyle.paddingTop != null && !isc.isAn.emptyString(handleStyle.paddingTop) &&
            isc.endsWith(handleStyle.paddingTop, pxString)) {
                padding.top = parseInt(handleStyle.paddingTop);
        }
        if (handleStyle.paddingBottom != null && !isc.isAn.emptyString(handleStyle.paddingBottom) &&
            isc.endsWith(handleStyle.paddingBottom, pxString)) {
                padding.bottom = parseInt(handleStyle.paddingBottom);
        }

        if (handleStyle.paddingLeft != null && !isc.isAn.emptyString(handleStyle.paddingLeft) &&
            isc.endsWith(handleStyle.paddingLeft, pxString)) {
                padding.left = parseInt(handleStyle.paddingLeft);
        }

        if (handleStyle.paddingRight != null && !isc.isAn.emptyString(handleStyle.paddingRight) &&
            isc.endsWith(handleStyle.paddingRight, pxString)) {
                padding.right = parseInt(handleStyle.paddingRight);
        }

        // If the padding is not set here, this will continue to check the CSS class style

    // if the widget is not drawn, but this.padding was specified, that takes precidence over the
    // css class applied to the widget.
    } else {
        if (this.topPadding != null) padding.top = this.topPadding;
        if (this.leftPadding != null) padding.left = this.leftPadding;
        if (this.rightPadding != null) padding.right = this.rightPadding;
        if (this.bottomPadding != null) padding.bottom = this.bottomPadding;
        if (this.padding != null) {
            var paddingAttr = parseInt(this.padding);
            if (padding.left == null) padding.left = paddingAttr;
            if (padding.top == null) padding.top = paddingAttr;
            if (padding.bottom == null) padding.bottom = paddingAttr;
            if (padding.right == null) padding.right = paddingAttr;
        }
    }

    // Having looked at the handle (or 'padding' property for undrawn widgets), if we have not
    // determined sizes for any side, derive the padding sizes from the css class applied to the
    // widget.
    if (this.className) {
        if (!isc.isA.Number(padding.left)) padding.left = isc.Element._getLeftPadding(this.className);
        if (!isc.isA.Number(padding.right)) padding.right = isc.Element._getRightPadding(this.className);
        if (!isc.isA.Number(padding.top)) padding.top = isc.Element._getTopPadding(this.className);
        if (!isc.isA.Number(padding.bottom)) padding.bottom = isc.Element._getBottomPadding(this.className);
	} else {
        // Padding not explicitly set == padding is zero
        if (!isc.isA.Number(padding.left)) padding.left = 0;
        if (!isc.isA.Number(padding.right)) padding.right = 0;
        if (!isc.isA.Number(padding.top)) padding.top = 0;
        if (!isc.isA.Number(padding.bottom)) padding.bottom = 0;
    }

    
    return (this._cachedPadding = padding);
},


// Containment and Intersection
// --------------------------------------------------------------------------------------------


//>	@method	canvas.containsPoint()  ([A])
// Return whether or not this object contains the specified global (x,y) coordinates.
// <P>
// Will return false if any parentElement does not contain the specified point, (EG:
// you're hovering over an element's absolute location, but it is scrolled out of
// view in a parent element)
//
//      @visibility external
//		@group	positioning
//
//		@param	x		(int)	GLOBAL x-coordinate
//		@param	y		(int)	GLOBAL y-coordinate
//		@param	[withinViewport]	(Boolean)	point lies specifically within our viewport
//                                              (drawn area excluding margins and scrollbars if
//                                              present)
//
//		@return	(Boolean)	true if this object contains the specified point; false otherwise
//<
containsPoint : function (x, y, withinViewport) {
    if (isc._traceMarkers) arguments.__this = this;
    // always bail if we're not visible
    if (!this.isVisible() || !this.isDrawn()) return false;

    
    if (withinViewport == null) withinViewport = false;

    // as a quick initial check, see if the point is within the page rect at all
    
    // Note: don't return true if the specified point is over our margin.
    var myPageLeft = this.getPageLeft() + this.getLeftMargin();
    if (x < myPageLeft) {
        
        return false;
    }
    var myPageTop = this.getPageTop() + this.getTopMargin();

    if (y < myPageTop) {
        

        return false;
    }
    var myWidth = withinViewport ? this.getViewportWidth()
                                 : (this.getVisibleWidth() - this.getHMarginSize());
    if (x > myPageLeft + myWidth) {
        
        return false;
    }
    var myHeight = withinViewport ? this.getViewportHeight()
                                  : (this.getVisibleHeight() - this.getVMarginSize());

    if (y > myPageTop + myHeight) {
        
        return false;
    }
    var pageLeft = 0, pageTop = 0;

    // Iterate through any parent elements, verifying that the point is contained in their
    // viewports
    
    // Use 'getCanvasLeft()' / 'getCanvasTop()' to determine the page level position of
    // each parentElement, by summing these values from the top level element.

    // create an array consisting of this widget and all parents
    var parentChain = this._parentChain = this._parentChain || []; // reuse an array
    parentChain.length = 1;
    parentChain[0] = this;

    var i = 1, currentParent = this;
    while (currentParent.parentElement != null) {
        currentParent = currentParent.parentElement
        parentChain[i] = currentParent;
        i++;
    }

    var viewportWidth, viewportHeight;

    // iterate backwards through the array, from top level parent down to us.
    // If we hit a case where we're not in the viewport, quit.
    for (var j = parentChain.length - 1; j >= 0 ; j--) {

        var widget = parentChain[j];

        pageLeft += widget.getCanvasLeft();
        pageTop += widget.getCanvasTop();

        

        if (widget == this && !withinViewport) {
            // respect the withinViewport flag
            viewportWidth = widget.getVisibleWidth() - widget.getHMarginSize();
            viewportHeight = widget.getVisibleHeight() - widget.getVMarginSize();
        } else {

            
            
            pageLeft += widget.getLeftBorderSize();
            pageTop += widget.getTopBorderSize();

            viewportWidth = widget.getViewportWidth();
            viewportHeight = widget.getViewportHeight();
        }
        

        

      	if  ( !((x >= pageLeft) && (x <= pageLeft + viewportWidth) &&
               (y >= pageTop) && (y <= pageTop + viewportHeight))       )
        {
            
            return false;
        }
    }
    return true;
},

//>	@method	canvas.visibleAtPoint()  ([A])
// Does this widget contain the specified global (x,y) coordinates, and have no other widgets
// also at the specified position, obscuring this one?  This is commonly used for (for example)
// drag and drop interactions.
//
//      @visibility external
//		@group	positioning
//
//		@param	x		(number)	GLOBAL x-coordinate
//		@param	y		(number)	GLOBAL y-coordinate
//		@param	[withinViewport]	(boolean)	point lies within our viewport rather than
//                                              just our drawn area
//      @param  [ignoreWidgets]  (canvas)    If passed ignore widget(s), do not check whether
//                                          those widgets occludes this one.
//      @param [upToParent] (Canvas) If passed, only check for siblings occluding the
//              component up as far as the specified parent widget.
//
//		@return	(boolean)	true if this object contains the specified point; false otherwise
//<

visibleAtPoint : function (x, y, withinViewport, ignoreWidgets, upToParent) {
    if (isc._traceMarkers) arguments.__this = this;

    

    
    if (!this.containsPoint(x,y,withinViewport)) {
        
        return false;
    }

    if (!isc.isAn.Array(ignoreWidgets)) ignoreWidgets = [ignoreWidgets];

    // To determine whether there are any widgets obscuring this widget from the specified
    // point we need to check whether our siblings and the siblings of each of our parents i
    // are positioned over the point and have a higher z-index than this widget.
    var currentWidget = this;
    
    while (currentWidget != null && currentWidget != upToParent) {
        var siblings = (currentWidget.parentElement != null ?
                        currentWidget.parentElement.children :
                        isc.Canvas._topCanvii);
        for (var i = 0; i < siblings.length; i++) {
            var sibling = siblings[i];
            // avoid checking this widget, any ignoreWidgets, and any widgets tagged
            // with isMouseTransparent:true
            if (sibling == null || sibling == currentWidget ||
                !sibling.isDrawn() || !sibling.isVisible() ||
                ignoreWidgets.contains(sibling) ||
                sibling.isMouseTransparent ||
                (sibling.getZIndex() < currentWidget.getZIndex()))
            {
                continue;
            }

            // can't be occluded by event mask
            if (sibling._maskTarget) continue;

            // You can't be occluded by a sibling's scrollbar/thumb without also being occluded
            // by the master of that scrollbar.
            // You could be occluded by your parent's scrollbar, iff the parent was both H&V
            // scrolling, but drag scrolling should bring you into view immediately, so we
            // ignore this case.
            if (isc.isA.Scrollbar(sibling) || isc.isA.ScrollThumb(sibling)) continue;

            // ignore edges generated by showEdges:true.  Note DropShadow is always
            // mouseTransparent
            if (isc.EdgedCanvas && isc.isA.EdgedCanvas(sibling) &&
                sibling.masterElement &&
                sibling.masterElement._edgedCanvas == sibling) continue;

            // Layouts never allow members to occlude each other, so skip the sibling if both
            // the current parent under consideration and it's sibling are members of a Layout
            if (isc.Layout && isc.isA.Layout(sibling.parentElement) &&
                sibling.parentElement.hasMember(sibling) &&
                sibling.parentElement.hasMember(currentWidget))
            {
                
                continue;
            }

            // ignore TabBars within TabSets when we are in the paneContainer (one day
            // TabSet will probably become a Layout and this check will be redundant with the
            // above)
            if (isc.TabSet && isc.isA.TabBar(sibling) &&
                isc.isA.TabSet(sibling.parentElement) &&
                sibling.parentElement.paneContainer &&
                sibling.parentElement.paneContainer.contains(this))
            {
                //this.logWarn("eliminated TabBar: " + sibling +
                //             " because we are in the paneContainer of TabSet: " +
                //             sibling.parentElement);
                continue;
            }

            
            if (sibling.containsPoint(x, y, false)) {
                
                return false;
            }
        }
        currentWidget = currentWidget.parentElement;
    }

    
    return true;
},


//>	@method	canvas.scrollIntoView()
//			Scrolls the widget such that the passed in x / y coordinates (relative to the
//          widget content) are visible in the viewport if they previously were not.
//
//		@group	positioning
//
//		@param	x		(number)	x-coordinate (relative to widget content)
//		@param	y		(number)	y-coordinate (relative to widget content)
//		@param	width   (number)	width of the rect to scroll into view - optional
//		@param	height  (number)	height of the rect to scroll into view - optional
//      @param  [xPosition] (string)    Where the target rectangle should show up in this
//                                      widget's viewport. Valid options are <code>"left"</code>
//                                      <code>"center"</code> or <code>"right"</code>. Defaults
//                                      to <code>"center"</code>.
//      @param  [yPosition] (string)    Where the target rectangle should show up in this
//                                      widget's viewport. Valid options are <code>"top"</code>
//                                      <code>"center"</code> or <code>"bottom"</code>. Defaults
//                                      to <code>"center"</code>.
//      @param  [animated]  (boolean)   If true, scrolling will be performed as an animation
//      @param [callback]   (callback)  Callback to fire when scrollIntoView completes.
//                                      Typically would only be passed in for animated
//                                      scroll, but will be fired after synchronous scroll too.
//                                      Will also be fired if this method does not actually
//                                      cause this widget's scroll position to change.<br>
//                                      Takes no parameters, but will be executed in the scope
//                                      of this widget.
//      @param [source]     (source)    The widget is a source calling scroolIntoView. It is set
//                                      to <code>"this"</code> for the first call the method.
//                                      It is used for recursive calls (scrollIntoView).
//<
_$left:"left", _$top:"top", _$right:"right", _$bottom:"bottom", _$center:"center",
scrollIntoView : function (x,y, width, height, xPosition, yPosition, animated, callback, alwaysCenter, source) {
    // If not passed a width / height, just scroll the point into view
    if (width == null) width = 0;
    if (height == null) height = 0;

    if (source == null) {
        // This is the first method call in the call chain
        source = this;
    }

    if (this.isRTL()) {
        // if we're in RTL mode, x will be < 0 if we're overflowing to the left.
        // our scroll coords start at zero however, so map across to the same space.
        x = this._shiftScrollLeftOrigin(x, false);
    }

    var synchronousCallback = true;

    var desiredScrollLeft, desiredScrollTop;

    if (this.overflow != isc.Canvas.VISIBLE &&
        this.overflow != isc.Canvas.IGNORE) {

        if (x != null) {
            var scrollLeft = this.getScrollLeft(),
                viewportWidth = this.getViewportWidth(),
                scrollRight = scrollLeft + viewportWidth,
                rightOff = false,
                leftOff = false;

            if (x + width > scrollRight) {
                rightOff = true;
            }
            if (x < scrollLeft) {
                leftOff = true;
            }

            // if the right edge is off, or the left edge is off, but not both, we need to
            // scroll.
            // (If they're both off, on different sides, then the rect is greater than the
            // viewport and there's nothing we can do)
            if (rightOff != leftOff || alwaysCenter) {
                if (xPosition == this._$left) {
                    desiredScrollLeft = x;
                // Align the right edge with the right edge of the viewport
                } else if (xPosition == this._$right) {
                    desiredScrollLeft = (x + width) - this.getViewportWidth();

                // Centering is the default case
                } else {
                    desiredScrollLeft = (x + parseInt(width/2))
                                        - parseInt(this.getViewportWidth() / 2);
                }
            }
        }

        if (y != null) {
            var scrollTop = this.getScrollTop(),
                scrollBottom = scrollTop + this.getViewportHeight(),
                topOff = false,
                bottomOff = false;

            if (y + height > scrollBottom) bottomOff = true;
            if (y < scrollTop) topOff = true;

            // if the top edge is off, or the bottom edge is off, but not both we need to
            // scroll.
            // (If they're both off, on different sides, then the rect is greater than the
            // viewport and there's nothing we can do)
            if (topOff != bottomOff || alwaysCenter) {
                if (yPosition == this._$top) {
                    desiredScrollTop = y;
                } else if (yPosition == this._$bottom) {
                    desiredScrollTop = (y + height) - this.getViewportHeight();
                } else {
                    desiredScrollTop = (y + parseInt(height/2))
                                         - parseInt(this.getViewportHeight() / 2);
                }
            }
        }
        // Note - if we don't have to scroll, avoid calling scrollTo, as this can take a little time
        if (desiredScrollLeft != null || desiredScrollTop != null) {
            //>Animation
            if (animated) {
                this.animateScroll(desiredScrollLeft, desiredScrollTop, callback);
                synchronousCallback = false;
            } else {
            //<Animation

            this.scrollTo(desiredScrollLeft, desiredScrollTop, "scrollIntoView");
            //>Animation
            }
            //<Animation
        }
    }

    // At this point we may be done, or we may have parent elements whos viewports we're not
    // visible through.
    if (this.parentElement != null) {
        var parentLeft = x, parentTop = y;
        if (parentLeft != null) {
            // If scrolling is not animated we've scrolled to desired scrollLeft / top -
            // otherwise we will end up there, so adjust the x/y to account for this.
            parentLeft -= (desiredScrollLeft != null ? desiredScrollLeft : this.getScrollLeft());
            parentLeft += this.getOffsetLeft();
        }
        if (parentTop != null) {
            parentTop -= (desiredScrollTop != null ? desiredScrollTop : this.getScrollTop());
            parentTop += this.getOffsetTop();
        }
        
        this.parentElement.scrollIntoView(parentLeft, parentTop, width, height, null, null, null, null, null, source);
    }

    if (callback && synchronousCallback) this.fireCallback(callback);
},


//>	@method	canvas.intersects() ([])
//			Returns true if the rectangles of this widget and the specified widget overlap.
//      @visibility external
//		@group	positioning
//		@param	other		(canvas)	other canvas to test for intersection
//		@return	(Boolean)	true if this canvas intersects other; false otherwise
//<
intersects : function (other){

	var otherLeft = other.getPageLeft(),
		otherWidth = other.getVisibleWidth(),
		otherTop = other.getPageTop(),
		otherHeight = other.getVisibleHeight()
	;
    return this.intersectsRect(otherLeft, otherTop, otherWidth, otherHeight)
},

//>	@method	canvas.intersectsRect() ([])
//			Returns true if the rectangle of this widget intersects with the rectangle coordinates
//          passed in, and false otherwise.
//      @visibility external
//		@group	positioning
//
//		@param	left		(number, array)	left coord of rect (or rect array)
//		@param	top 		(number)	    top coord of rect
//		@param	width		(number)	    width of rect
//		@param	height		(number)	    height of rect
//
//		@return	(boolean)	true if this canvas intersects the rectangle passed in; false otherwise
//<
intersectsRect : function (left, top, width, height){
    var rect1, rect2 = [];

    if (isc.isAn.Array(left)) rect1 = left;
    else rect1 = [left, top, width, height];

    return isc.Canvas.rectsIntersect(rect1, [this.getPageLeft(), this.getPageTop(),
                                             this.getVisibleWidth(), this.getVisibleHeight()]);
},

// Interior Coordinates
// --------------------------------------------------------------------------------------------

//>	@method	canvas.containsEvent()
//			Return true if the last event's mouse coordinates are within the bounds of this component.
//		NOTE: Z-ordering is not considered for the purposes of this test.  If the coordinate you're
//		testing is occluded by other component, but the X,Y coordinates are still within the bounds
//		of that component, this method will return true.
//
//		@group	events, positioning
//
//		@return	(Boolean)	true if the event occurred within the bounds of this component
// @visibility external
//<
containsEvent : function () {
	return this.containsPoint(this.ns.EH.getX(), this.ns.EH.getY());
},

//>	@classMethod canvas.getEventEdge()
// Check if an event is within an "edge" of this canvas.
//
// @param [edgeMask] (Array of EdgeName) Array of legal edges.  Default is all the edges that
//                                       allow resizing (see +link{resizeFrom})
//
// @return (EdgeName) edge where the mouse is positioned, or null if not within a legal edge
//                    (including being in the center)
// @group dragdrop, dragResize
// @see attr:canvas.resizeFrom
// @visibility external
//<
getEventEdge : function (edgeMask) {
	var EH = this.ns.EH;
	if (!edgeMask) edgeMask = (this.resizeFrom || EH.ALL_EDGES);
	var margin = this.edgeMarginSize;

	if (!isc.isAn.Array(edgeMask)) edgeMask = [edgeMask];

	// get various sizes, etc. to make the logic below cleaner
    // Note: coordinates reported are relative to outside our Margins. Adjust to get the
    // coordinates over the widget's actual handle
    
    var margins = this._getSpecifiedMargins(),
        leftMargin = margins.left,
        rightMargin = margins.right,
        topMargin = margins.top,
        bottomMargin = margins.bottom;


    var pageOffsets = this.getPageOffsets(),
        left = pageOffsets.left + leftMargin,
        top = pageOffsets.top + topMargin,
        // 2002.2.25 outset rect by 1 to fix problems in IE where exactly on the edge
        // doesn't register properly
		right = (this.getPageRight(pageOffsets) - rightMargin) + 1,
		bottom = (this.getPageBottom(pageOffsets) - bottomMargin) + 1,
		y = EH.getY(),
		x = EH.getX(),
		hEdge = "",
		vEdge = ""
	;

    //this.logWarn("x,y: " + [x,y] + ", rect: " + [left,top,right,bottom]);

	// if the mouse is not within this Canvas at all, bail
	if (y < top || y > bottom || x < left || x > right)	return null;

	// figure out what side/corner of the target we're in, if any

	// is it inside the top or bottom edge ?  (Bottom takes precedence over top)
	if 		(y >= (bottom - margin) && y <= bottom)			 	vEdge = "B";
	else if (y >= top 				&& y <= (top + margin + 1)) 	vEdge = "T";

	// is it inside the left or right edge ?  (Right takes precedence over left)
	if 		(x >= (right - margin)	&& x <= right) 				hEdge = "R";
	else if (x >= left 				&& x <= (left + margin + 1)) 	hEdge = "L";

	// if we're in some edge
	if (hEdge != "" || vEdge != "") {
		var resizeCorner = vEdge + hEdge;
        // figure out if we're in a valid corner, which takes precedence over an edge
		if (edgeMask.contains(resizeCorner)) 	return resizeCorner;
		// not in a valid corner, check for valid edge (horizontal takes precedence)
		else if (hEdge != "" && edgeMask.contains(hEdge))		return hEdge;
		else if (vEdge != "" && edgeMask.contains(vEdge))		return vEdge;
	}

	// no legal corner or edge found -- forget it!
	return null;
},


//>	@method	canvas.getOffsetX()
//	Return the X-coordinate of the last event relative to the left edge of the content of this
//	Canvas.<br><br>
//
//  NOTE: To get a coordinate relative to the <b>viewport</b> of this Canvas, subtract
//  this.getScrollLeft()
//
//	@group	events, positioning
//	@return	(number)
//	@visibility external
//<
getOffsetX : function () {
    var value = this.ns.EH.getX()
        - (this.getPageLeft() + this.getLeftBorderSize())
        + this.getScrollLeft()
        // textDirection: if the canvas is drawn RTL and the vertical scrollbar is visible, it
        // will be on the left of the content, and we don't want to count it as part of the
        // canvas, so subtract the scrollbarSize from the offsetX
        - (this.vscrollOn && this.isRTL() ? this.getScrollbarSize() : 0);

    return value;
},


//>	@method	canvas.getOffsetY()
//	Return the Y-coordinate of the last event, relative to the top edge of the content of this
//	Canvas.<br><br>
//
//  NOTE: To get a coordinate relative to the <b>viewport</b> of this Canvas, subtract
//  this.getScrollTop()
//
//	@group	events, positioning
//	@return	(number)
//	@visibility external
//<
getOffsetY : function () {
    return this.ns.EH.getY()
                + this.getScrollTop()
                - (this.getPageTop() + this.getTopBorderSize());
},


// Visible Area
// --------------------------------------------------------------------------------------------




//>	@method	canvas.setClip()	(A)
// Set the clip region of this handle
//
// NOTE: you can pass an array in TRBL order as the first parameter instead
//
//		@group	sizing
//
//		@param	top			(number)	new top clip coordinate
//		@param	right		(number)	new right clip coordinate
//		@param	bottom		(number)	new bottom clip coordinate
//		@param	left		(number)	new left clip coordinate
//<
setClip : function (top, right, bottom, left) {

    // store the values in the 'clip' slot
    if (isc.isAn.Array(top))
        this._clip = top;
    else
        this._clip = [top, right, bottom, left];

    // if the layer has been drawn, set its clip!
    var clipHandle = this.getClipHandle();
    if (clipHandle != null) {

        var clip = this._clip;

        

        

        // actually set the clip
        clipHandle.style.clip = "rect("+ clip.join("px ")+"px)";
    }
},

//>	@method	canvas.getScrollbarSize()	(A)
//  Returns the thickness of this widget's scrollbars.<br>
//  For canvases showing custom scrollbars this is determined from <code>this.scrollbarSize</code>
//
//	@group	scrolling
//	@return	(number) thickness of the scrollbars, in pixels
//	@visibility external
//  @see    scrollbarSize
//<
getScrollbarSize : function () {
    if (this.showCustomScrollbars) return this.getCustomScrollbarSize();
    return isc.Element.getNativeScrollbarSize();
},

//>	@method	canvas.getViewportWidth()	(A)
//  Returns the width of the viewport onto the scrollable content.
//
//	@group	sizing
//
//	@return	(number) width of the viewport, in pixels
//	@visibility external
//<
getViewportWidth : function() {
    return this.getVisibleWidth() -
                (this.vscrollOn ? this.getScrollbarSize() : 0) -
                this.getHMarginBorder();
},

//>	@method	canvas.getViewportHeight()	(A)
//  Returns the height of the viewport onto the scrollable content.
//
//	@group	sizing
//
//	@return	(number) height of the viewport, in pixels
//	@visibility external
//<
getViewportHeight : function() {
    return this.getVisibleHeight() -
                (this.hscrollOn ? this.getScrollbarSize() : 0) -
                this.getVMarginBorder();
},

//>	@method	canvas.getOuterViewportWidth()	(A)
// Returns the outer width of the viewport - the width including any borders (but excluding
// any vertical scrollbar)
//	@group	sizing
//
//	@return	(number) width of the viewport, in pixels
//<
getOuterViewportWidth : function () {
    
    return this.getVisibleWidth() - (this.vscrollOn ? this.getScrollbarSize() : 0) -
           this.getHMarginSize();
},

//>	@method	canvas.getOuterViewportHeight()	(A)
// Returns the outer height of the viewport - the width including any borders (but excluding
// any horizontal scrollbar)
//
//	@group	sizing
//
//	@return	(number) height of the viewport, in pixels
//<
getOuterViewportHeight : function () {
    return this.getVisibleHeight() - (this.hscrollOn ? this.getScrollbarSize() : 0) -
           this.getVMarginSize();
},



//>	@method	canvas.getInnerHeight()	(A)
// Returns the amount of space available for (an) absolutely positioned child widget(s) or
// absolutely positioned HTML content, without introducing clipping, scrolling or overflow.
// <P>
// This is the space within the viewport of the widget (including padding, but excluding
// margins, borders or scrollbars) rendered at its specified size.
//
//	@group	sizing
//
//	@return	(number) inner height of the widget in pixels
//  @see Canvas.getInnerWidth()
//  @see Canvas.getInnerContentHeight()
//  @see Canvas.getInnerContentWidth()
//	@visibility external
//<
getInnerHeight : function() {
    return this.getHeight()
           - ((this.hscrollOn || this.overflow == isc.Canvas.SCROLL) ? this.getScrollbarSize()
                                                                     : 0)
           - this.getVMarginBorder();
},

//>	@method	canvas.getInnerWidth()	(A)
// Returns the amount of space available for absolutely positioned child widget(s) or
// absolutely positioned HTML content, without introducing clipping, scrolling or overflow.
// <P>
// This is the space within the viewport of the widget (including padding, but excluding
// margins, borders or scrollbars) rendered at its specified size.
//
//	@return	(number) inner width of the widget in pixels
//	@group	sizing
//  @see Canvas.getInnerHeight()
//  @see Canvas.getInnerContentHeight()
//  @see Canvas.getInnerContentWidth()
//  @visibility external
//<
getInnerWidth : function () {
    var width = this.getWidth();
    if (this.vscrollOn || this.overflow == isc.Canvas.SCROLL || this.alwaysShowVScrollbar)
        width -= this.getScrollbarSize();
    return width - this.getHMarginBorder();
},

//>	@method	canvas.getInnerContentHeight()	(A)
// Returns the amount of space available for interior content (or relatively positioned child
// widget(s)) without introducing clipping, scrolling or overflow.<br>
// This is the space within the viewport of the widget (not including padding, and excluding
// margins, borders or scrollbars) rendered at its specified size.
//
//	@group	sizing
//
//	@return	(number) inner height of the widget in pixels
//  @see Canvas.getInnerContentWidth()
//  @see Canvas.getInnerHeight()
//  @see Canvas.getInnerWidth()
//	@visibility external
//<
getInnerContentHeight : function () {
    // Interior content space is the size of the handle (specified size less margins), minus
    // border and padding -- the total available space for a relatively positioned HTML element
    // without introducing overflow
    return Math.max(1, this.getHeight()
           - (this.hscrollOn || this.overflow == isc.Canvas.SCROLL ?
                    this.getScrollbarSize() : 0)
           - this.getVMarginBorderPad());

},


//>	@method	canvas.getInnerContentWidth()	(A)
//  Returns the amount of space available for interior content (or relatively positioned child
//  widget(s)) without introducing clipping, scrolling or overflow.<br>
//  This is the space within the viewport of the widget (not including padding, and excluding
//  margins, borders or scrollbars) rendered at its specified size.
//
//	@group	sizing
//
//	@return	(number) inner height of the widget in pixels
//  @see Canvas.getInnerContentHeight()
//  @see Canvas.getInnerHeight()
//  @see Canvas.getInnerWidth()
//	@visibility external
//<
getInnerContentWidth : function (visibleWidth) {
    
    var width = visibleWidth ? this.getVisibleWidth() : this.getWidth();
    if (this.vscrollOn || this.overflow == isc.Canvas.SCROLL || this.alwaysShowVScrollbar)
        width -= this.getScrollbarSize();
    return Math.max(1, width - this.getHMarginBorderPad());

},



// Per-axis accessors for border, margin, padding size
// ---------------------------------------------------------------------------------------

//>	@method	canvas.getVBorderPad()	(A)
//  Returns the total size of vertical (top and bottom) border and padding for this widget.
//
//	@group	sizing
//
//	@return	(number) vertical border and padding for this widget
//<
getVBorderPad : function () {
    return this.getVBorderSize() + this.getVPadding();
},

//>	@method	canvas.getHBorderPad()	(A)
//  Returns the total size of horizontal (left and right) border and padding for this widget.
//
//	@group	sizing
//
//	@return	(number) horizontal border and padding for this widget
//<
getHBorderPad : function () {
    return this.getHBorderSize() + this.getHPadding();
},

getHMarginSize : function () {
    return this.getLeftMargin() + this.getRightMargin();
},

getVMarginSize : function () {
    return this.getTopMargin() + this.getBottomMargin();
},


getVMarginBorder : function () {
    var margins = this._calculateMargins(),
        borders = this._calculateBorderSize();
    return margins.top + margins.bottom +
            borders.top + borders.bottom;
    //return this.getVMarginSize() + this.getVBorderSize();
},
getHMarginBorder : function () {
    var margins = this._calculateMargins(),
        borders = this._calculateBorderSize();
    return margins.left + margins.right +
            borders.left + borders.right;
    //return this.getHMarginSize() + this.getHBorderSize();
},

getVMarginBorderPad : function () {
    return this.getVMarginSize() + this.getVBorderPad();
},

getHMarginBorderPad : function () {
    return this.getHMarginSize() + this.getHBorderPad();
},

// Visible Dimensions
// ---------------------------------------------------------------------------------------

//>!BackCompat 2004.1.1 outdated synonyms of getVisibleHeight/Width
getClipWidth : function () { return this.getVisibleWidth(); },
getClipHeight : function () { return this.getVisibleHeight(); },
//<!BackCompat

//>	@method	canvas.getVisibleWidth()	(A)
//      Return the visible width of the Canvas.
//
//		@group	sizing
//
//		@return	(number) visible width in pixels
//  @visibility external
//<
// Note this width includes any margin for the item - essentially it's the space required to
// render the widget

getVisibleWidth : function (recalc) {
    if ((this._drawn || this._handleDrawn) &&
        (this.overflow == isc.Canvas.VISIBLE || this.overflow == isc.Canvas.CLIP_V)) {
        // if the overflow is visible, the visible width may be greater than the
        // specified width
        return Math.max(this.width,
                        (this.getScrollWidth(recalc) + this.getHMarginBorder()));
    } else {
        // overflow is Hidden, Auto, Scroll, CLIP_H or Ignore.
        // Return the specified width
        //>Animation
        // If we're doing an animated hide/show verify adjust for rendered scrollbar size if
        // necessary
        var animationInfo = this.isAnimating(this._$show) ? this.$showAnimationInfo :
                            this.isAnimating(this._$hide) ? this.$hideAnimationInfo : null;
        if (animationInfo != null && !animationInfo._vertical && this.vscrollOn) {
            var sbDelta = 0;
            if (this.vscrollbar.visibility == isc.Canvas.HIDDEN) {
                sbDelta = this.getScrollbarSize();
            } else {
                sbDelta = this.getScrollbarSize() - this.getScrollbarSize();
            }
            return Math.max(this.getWidth() - sbDelta,1);
        }
        //<Animation
        return this.getWidth();
    }
},

//>	@method	canvas.getVisibleHeight()	(A)
//      Return the visible height of the Canvas.
//
//		@group	sizing
//
//		@return	(number) visible height in pixels
//  @visibility external
//<
getVisibleHeight : function (recalc) {
    if ((this._drawn || this._handleDrawn) &&
        (this.overflow == isc.Canvas.VISIBLE || this.overflow == isc.Canvas.CLIP_H))
    {
        // if the overflow is visible, the visible height may be greater than the
        // specified height
        return Math.max(this.getHeight(),
                        (this.getScrollHeight(recalc) + this.getVMarginBorder()));

    } else {
        //>Animation
        // During animateShow() / animateHide(), with wipe/slide effect, we resize and hide the
        // scrollbar on the leading edge of the resize.
        // setHeight() assumes the scrollbar is fully visible, so at this point the specified height
        // could exceed the rendered height by the size of the scrollbar.
        // Explicitly catch this case and return the smaller size.
        // This ensures that when animateHide() / animateShow()ing members of a layout the reflow
        // respects tha actual space taken up by the member during the animation rather than being
        // off by up to one scrollbarSize
        
        if (this.isAnimating()) {
            var animationInfo = this.isAnimating(this._$show) ? this.$showAnimationInfo :
                                this.isAnimating(this._$hide) ? this.$hideAnimationInfo : null;
            if (animationInfo != null && animationInfo._vertical && this.hscrollOn) {
                var sbDelta = 0;
                if (this.hscrollbar && this.hscrollbar.visibility == isc.Canvas.HIDDEN) {
                    sbDelta = this.getScrollbarSize();
                } else {
                    sbDelta = this.getScrollbarSize() - this.getScrollbarSize();
                }
                return Math.max(this.getHeight() - sbDelta,1);
            }
        }
        //<Animation
        // overflow is Hidden, Auto, Scroll, CLIP_V, or Ignore.
        // Return the specified height
        return this.getHeight();
    }
},

getPeerRect : function () {
    var rect = this.getPageRect();
    if (this.peers == null) return rect;
    for (var i = 0; i < this.peers.length; i++) {
        var peer = this.peers[i];
        // NOTE: only ignore a peer that is explicitly not visible while we are visible
        if (!peer.isDrawn() || (this.isVisible() && !peer.isVisible())) continue;

        // Special case: If we are hidden, and we have hidden scrollbars, they may or may not
        // show with us.
        // Furthermore, when we adjustOverflow(), if scrollbars are no longer required we hide
        // them, but don't bother resizing them (if thats required), so they can effect the
        // size reported by this method when they shouldn't
        // Use vscrollOn / hscrollOn to avoid taking them into account if they're not currently
        // meant to be showing.
        if ((!this.vscrollOn && peer == this.vscrollbar) ||
            (!this.hscrollOn && peer == this.hscrollbar)) continue;

        var peerRect = peer.getPageRect();

        if (peerRect[0] < rect[0]) rect[0] = peerRect[0];
        if (peerRect[1] < rect[1]) rect[1] = peerRect[1];
        // NOTE: a peer may extend to the right/bottom while being smaller than it's master
        var peerRight = peerRect[0] + peerRect[2];
        if (peerRight > rect[0] + rect[2]) rect[2] = peerRight - rect[0];
        var peerBottom = peerRect[1] + peerRect[3];
        if (peerBottom > rect[1] + rect[3]) rect[3] = peerBottom - rect[1];
    }
    return rect;
},

// Moving
// --------------------------------------------------------------------------------------------


//>	@method	canvas.moveBy() ([])
//			Moves the widget deltaX pixels to the right and deltaY pixels down. Pass negative
//          numbers to move up and/or to the left.
//      @visibility external
//		@group	positioning
//		@param	deltaX		(int)	amount to move horizontally (may be negative)
//		@param	deltaY		(int)	amount to move vertically (may be negative)
//		@return	(Boolean)	whether the component actually moved
//      @example    move
//<
//>Animation
// @param [animating] (boolean) Internal parameter passed if this move is being called as part
//  of an animation
//<Animation

moveBy : function (deltaX, deltaY, animating, resizeHandle) {
    //>Animation
    // If an external moveBy is called during an animated setRect, finish the animated setRect
    // before starting the explicit move.
    // Note: it's a setRect if resizeHandle is true, and a straight move otherwise
    var setRectAnimating = animating && resizeHandle;
    if (!setRectAnimating && this.rectAnimation) this.finishAnimation("rect");
    else if (!animating && this.moveAnimation) this.finishAnimation("move");
    //<Animation

    if (isc._traceMarkers) arguments.__this = this;

	// adjust our internal values by values passed in
	if (isc.isA.Number(deltaX))
		this.left += deltaX;
	else
		deltaX = 0;

	if (isc.isA.Number(deltaY))
		this.top += deltaY;
	else
		deltaY = 0;

    
    var moved = (deltaX != 0 || deltaY != 0);
    if (!moved && !resizeHandle) return false;

    // store the deltas locally - used by _completeMoveBy()
    this._moveDeltaX = deltaX;
    this._moveDeltaY = deltaY;

    
    var width = (resizeHandle && this._resizeDeltaX ? this.width : null),
        height = (resizeHandle && this._resizeDeltaY ? this._height : null);

    this._setHandleRect(this.left, this.top, width, height);
    if (resizeHandle) this._completeResizeBy();
    this._completeMoveBy();

    return moved;
},


_completeMoveBy : function () {

    var deltaX = (this._moveDeltaX || 0),
        deltaY = (this._moveDeltaY || 0),
        undef;

    this._moveDeltaX = undef;
    this._moveDeltaY = undef;

    // Just bail if this method was called with no move required.
    
    if (!deltaX && !deltaY) return;

    // fire up/down chain parent and child / master and peer notifications
    this._fireParentMoved(this, deltaX, deltaY);
    this._fireMasterMoved(deltaX, deltaY);
    if (this.parentElement) this.parentElement.childMoved(this, deltaX, deltaY);
    if (this.masterElement) this.masterElement.peerMoved(this, deltaX, deltaY);

    //>FocusProxy If we have a focusProxy written into the DOM, move it so it continues to
    // float over this widget.
    if (this._useFocusProxy && this._hasFocusProxy) {
        var fpp = this._getFocusProxyParentHandle();
        if (fpp != null) {
            var newLeft = parseInt(fpp.style.left) + deltaX,
                newTop = parseInt(fpp.style.top) + deltaY;
            fpp.style.left = newLeft + "px";
            fpp.style.top = newTop + "px";
        }
    } //<FocusProxy

    

    // call the observable moved method

    
    this._$leftCoords = this._$topCoords = null;
    this.handleMoved(deltaX, deltaY);
},

handleMoved : function (deltaX, deltaY) {
	// when a top level element is moved or resized it can introduce page level scrollbars, changing
	// the browser window size overall.
	// We don't get a resized event notification from the browser on this, so explicitly run the
	// _pageResize() method
	
	if (!this._pageResizing && this.isDrawn()
		&& this.parentElement == null && !isc.Page.pollPageSize)
	{
		isc.EH.fireOnPause("checkForBodyOverflowChange",
							{target:isc.Canvas, methodName:"checkForPageResize"},
							100);
    }
	this.moved(deltaX, deltaY);
},

// canvas.moved()
//  Observable method called whenever a Canvas is explicitly moved.
//  Documented under registerStringMethods
moved : function (deltaX, deltaY) {

//!DONTOBFUSCATE  (we want observers to be able to pick up the passed values)
},


// canvas.parentMoved()
//  Observable method called whenever a Canvas's ancestor is explicitly moved.
//  Documented under registerStringMethods
parentMoved : function (parent, deltaX, deltaY) {
},

// If our parent has moved, inform any children we have that an ancestor has moved.
// This notifies the children that they will have been repositioned in terms of page
// coordinates.
handleParentMoved : function (parent, deltaX, deltaY) {

    
    this._$leftCoords = this._$topCoords = null;
	this.parentMoved(parent, deltaX, deltaY);

	// fireParentMoved is what notifies our children (recursively) that we moved.
	this._fireParentMoved(parent, deltaX, deltaY);
},

// fire 'handleParentMoved' on children. This will recursively call back into this method to
// notify all descendents.
_fireParentMoved : function (parent, deltaX, deltaY) {
	var children = this.children;
	if (children != null) {
		for (var i = 0; i < children.length; i++) {
			// NOTE: this fires before during init, before children have necessarily been
			// auto-created
			if (isc.isA.Canvas(children[i])) {
				children[i].handleParentMoved(parent, deltaX, deltaY);
			}
		}
    }
},


// parent receiving notification that a child has moved
_$childMoved : "childMoved",
childMoved : function (child, deltaX, deltaY) {
    // containedPeer means this child is a peer of some other element that intends to keep the
    // child completely within it's bounding box, hence, it should not trigger adjustOverflow.
    // Examples include the ScrollThumb and the label for StretchImgButtons
    if (child && child.masterElement != null && child.containedPeer == true) return;

    // if a child moves, the size of our content may have changed, so adjustOverflow.  For
    // example, we may need to grow/shrink to fit (overflow:visible), or show or hide scrollbars
    // (overflow:auto).
    
    if (this.allowContentAndChildren && this.overflow == isc.Canvas.VISIBLE)
        this._resetHandleOnAdjustOverflow = true;

    this._markForAdjustOverflow(this._$childMoved);
},

_fireMasterMoved : function (deltaX, deltaY) {
    var peers = this.peers;
    if (peers == null) return;
    for (var i = 0; i < peers.length; i++) {
        if (peers[i]) peers[i].masterMoved(deltaX, deltaY);
    }
},
masterMoved : function (deltaX, deltaY) {
    if (this._moveWithMaster) this.moveBy(deltaX, deltaY);
    // NOTE: not a recursive notification
},

// master receiving notification that a peer has moved
peerMoved : function (child, deltaX, deltaY) { },

//> @method canvas.dragRepositioned()    (A)
// Observable function fired once at the end of a successful drag-reposition operation.
// Useful for firing some action in response to reposition without firing repeatedly on every
// dragMove while the user is drag-resizing the target.
//<
dragRepositioned : function () {},

// Percent / "*" coordinate handling
// --------------------------------------------------------------------------------------------
// Special coordinate specifications like percents and "*" must get resolved into pixel values
// before the widget is drawn.
// We handle this by resolving these coordinates to pixel values on widget init(), (or on
// setWidth() / setHeight()), and storing the pixel value as this.width (available via
// this.getWidth()).
// The original string value is stored in a second variable and updated when it's meaning
// changes (eg a percent's resolved value changing on parent/page resize).
//   - percents are resolved as percents of the parent size, or of the page size if we're at top
//     level
//   - "*" values are just destroyed, since they only matter in Layouts, and are the same as the
//     absence of a value

// get the delta between the coordinate of name 'name', new value 'coord', current value 'current
// value'.  Handles resolving percent coordinates to pixel values and discarding and logging bad
// values
_$height : "height",
_$width : "width",
_$left : "left",
_$top : "top",
_$_height: "_height",
_$percent: "%",
_$star : "*",
_percentNames : {
    height : "_percent_height",
    width : "_percent_width",
    left : "_percent_left",
    top : "_percent_top"
},
_minNames:{
    height:"minHeight",
    width:"minWidth"
},
_maxNames:{
    height:"maxHeight",
    width:"maxWidth"
},
getDelta : function (name, newValue, currentValue) {
    if (newValue == null) return null;

    
    var propertyName = name,
        percentName = this._percentNames[name];
    if (name == this._$height) propertyName = this._$_height;

    // If we were passed a fractional number, round it and warn.
    // Note we don't need to do this with percent values, which already get rounded
    // or numbers-as-strings (like "5") where we simply parseInt when converting.
    if (isc.isA.Number(newValue)) {
        var rounded = Math.round(newValue);
        if (rounded != newValue) {
            this.logWarn(name + " specified as fractional coordinate:"+ newValue +
                        ". Rounded to:" + rounded);
            newValue = rounded;
        }
    } else if (isc.isA.String(newValue) && isc.endsWith(newValue, this._$percent)) {

        // remember the percent version of this coordinate
        this[percentName] = newValue;


        // if this is a top-level widget with a percent coordinate, update whenever there is a
        // page resize event.  NOTE: this is FIRE_ONCE so we don't receive multiple resize
        // events; we reregister each time.
        if (this.masterElement == null && this.parentElement == null && this._resizeID == null) {
            this._resizeID = isc.Page.setEvent(this._$resize, this, isc.Page.FIRE_ONCE);
        }

        if (this._canvas_initializing) {
            // at init time only, ensure we report a non-zero delta as we resolve our
            // percentage size to a pixel size.  We are effectively going from an
            // unknown to a known size, so we want to trigger all logic associated
            // with size change.  Subsequently, a percent size widget reports normal deltas.
            currentValue = this[propertyName] = 0;
            
            if (this.percentBox == "custom") this[propertyName] = 1;
        }

        // "custom" percentBox - assume the parent will apply some custom logic to size / position
        // this child so suppress the standard handling
        if (this.percentBox == "custom") return 0;

        // get the relevant full size
        // this is the page width/height if this canvas has no parents, or
        // the parent element's inner width/height, otherwise
        var parent, fullSize, insideParent,
            horizontal = (name == this._$left || name == this._$width);
        // viewport vs outer size determined by percentBox setting
        if (this.percentSource || (this.snapTo && this.masterElement)) {
            parent = this.percentSource || this.masterElement;
            insideParent = (this.percentBox == this._$viewport),
            fullSize = horizontal ? (insideParent ? parent.getViewportWidth()
                                                  : parent.getVisibleWidth())
                                  : (insideParent ? parent.getViewportHeight()
                                                  : parent.getVisibleHeight());
        } else {
            parent = this.parentElement;
            fullSize = (horizontal ? (parent ? parent.getInnerWidth() : isc.Page.getWidth())
                                   : (parent ? parent.getInnerHeight(): isc.Page.getHeight())
                       );
        }




        // In IE and Chrome we can hit a case where page size is initially reported as
        // zero px. In this case we want to re-calculate top-level widget
        // percentage sizes after page load completes (When page size *is* available)

        

        //>IE
        if (isc.Browser.isIE && !isc.Page.isLoaded() &&
            ((isc.Page.getWidth() == 0) || (isc.Page.getHeight() == 0)))
        {
            isc.Page.setEvent(
            	"load",
            	"if(window[" + this.ID + "])" + this.ID + ".pageResize()", isc.Page.FIRE_ONCE
            );
            // set a flag to indicate this special case so we avoid attempting to draw() before
            // we've resized correctly
            this._pendingPageResizeForZeroSize = true;
        } //<IE

        
        if (isc.Browser.isChrome && (!isc.Page.isLoaded() || isc.EH._handlingEvent == "load") &&
            (isc.Page.getWidth() == 0 || isc.Page.getHeight() == 0))
        {
            if (isc.Page.isLoaded()) {
                isc.Page.setEvent("idle",
                    "if(window." + this.ID + ")" + this.ID + ".pageResize()",
                    isc.Page.FIRE_ONCE);
            } else {
                isc.Page.setEvent("load",
                    "if(window." + this.ID + ")" + this.ID + ".delayCall('pageResize',[],100)",
                    isc.Page.FIRE_ONCE);
            }
            this._pendingPageResizeForZeroSize = true;
        }
        // compute the coord as a percent of that
        
        newValue = Math.round((parseInt(newValue, 10) / 100) * fullSize);

        // support minWidth / minHeight for percent sizes.
        var min = this[this._minNames[name]];
        if (min != null && newValue < min) {
            newValue = min;
        }
        var max = this[this._maxNames[name]];
        if (max != null && newValue > max) {
            newValue = max;
        }

        //if (name == "height") {
        //    this.logWarn("resolved percent height ["+ this[percentName]+ "] to: " + newValue +
        //                 ", parent height: " + fullSize + ", currentValue: " + currentValue);
        //}
        return newValue - currentValue;
    }

    // handle coordinates specified as strings.  Even though we document this as incorrect, it's
    // really easy to forget if you work in XML.
    var origNewValue = newValue;
    if (!isc.isA.Number(newValue)) {
        newValue = parseInt(newValue);
        // if we parsed the newValue and got a valid number, and this is init time
        // (currentValue is a string, which can never happen after init), change the saved
        // value to the numeric version.
        if (isc.isA.Number(newValue) && isc.isA.String(currentValue)) {
            this[propertyName] = currentValue = newValue;
        }
    }

    // clear any previously defined percent size -- this is either a valid numeric size, or an
    // invalid value, in which case we'll revert to default size.
    this[percentName] = null;

    var layoutSetSize = false;

    // complain about bad coordinates.
    if (!isc.isA.Number(newValue) ||
        (newValue < 0 && (name == this._$width || name == this._$height)))
    {
        // HACK: avoid complaining about "*", which is valid within a Layout, and which can be
        // treated as the absence of a value.
        if (origNewValue != "*") {
            //>DEBUG
            this.logWarn("ignoring bad or negative " + name + ": " + origNewValue +
                         (this.logIsDebugEnabled("sizing") ? this.getStackTrace()
                          : " [enable 'sizing' log for stack trace]")); //<DEBUG
        } else {
            // HACK: setting width/height to "*" after init:
            // - this should mean the same thing "*" does before init, and the Layout normally
            //   picks up the "*" size via _userWidth/Height, since getWidth()/Height() always
            //   return pixels
            // - there is no clear delta value we can report - even if we reverted to default
            //   size there may be no change - so the usual childResized() notification that
            //   causes automatic reflow won't occur.  So we do it manually.
            //this.logWarn("clearing user prop: " + name);
            name == this._$width ? this._userWidth = "*" : this._userHeight = "*";
            var parent = this.parentElement;
            if (isc.isA.Layout(parent) && parent.hasMember(this)) {
                parent.reflow(this.getID() + " set " + name + " to '*'");
                layoutSetSize = true;
            }
        }

        // if the value we were initialized with is bad, remove it, hence reverting to
        // defaults.
        
        if (!layoutSetSize && (currentValue == this[name] || currentValue == this[propertyName]))
        {
            currentValue = this.restoreDefaultSize(name == this._$height);
        }
        // Fire adjustOverflow to actually resize the handle to the default size, if necessary
        this.adjustOverflow();

        return null;
    }

    //this.logWarn("getDelta: newValue: " + newValue + ", currentValue: " + currentValue);

    return newValue - currentValue;
},

restoreDefaultSize : function (isHeight) {
    
    var propertyName = isHeight ? this._$height : this._$width,
        instanceDefault = this.getClass().getInstanceProperty(propertyName);

    // use defaultHeight/Width if set
    if (!isc.isA.Number(instanceDefault)) {
        if (isHeight) instanceDefault = this.defaultHeight;
        else instanceDefault = this.defaultWidth;
    }

    var currentValue = this[propertyName] = (isc.isA.Number(instanceDefault) ?
                                             instanceDefault : 0);
    
    if (isHeight) this._height = currentValue;

    return currentValue;
},

// if we have any percent coordinates, recompute their values
pageResize : function () {
	this._pageResizing = true;
    //this.logWarn("pageResize: resizing to: " + [this._percentWidth, this._percentHeight] + " of " +
    //             [Page.getWidth(), Page.getHeight()] + this.getStackTrace());
    this._resizeID = null;
    // clear out the flag set up for handling the 'showModalDialog' case in IE
    this._pendingPageResizeForZeroSize = null;
    this._resolvePercentageSize();
    delete this._pageResizing;
},

//>	@method	canvas.moveTo() ([])
// Moves the widget so that its top-left corner is at the specified coordinates.
// <P>
// This method will also accept a single parameter as an object array with left and top given
// as properties.
//
//      @visibility external
//		@group	positioning
//		@param	[left]		(number or Object) x-coordinate to move to in LOCAL coordinates
//						                       or Object with left and top properties
//		@param	[top]		(number)	y-coordinate to move to in LOCAL coordinates
//		@return	(boolean)	whether the component actually moved
//      @example    move
//<
//>Animation
// @param [animating] (boolean) optional internal parameter passed if this moveTo is being
//   called as part of an animation.
//<Animation

moveTo : function (left, top, animating, resizeHandle) {
    if (!resizeHandle && left == null && top == null) return false;
    if (isc._traceMarkers) arguments.__this = this;

    if (left != null && left.top != null) {
        top = left.top;
        left = left.left;
    }

    var deltaX = this.getDelta(this._$left, left, this.getLeft()),
        deltaY = this.getDelta(this._$top, top, this.getTop());

    //if (deltaX != 0 || deltaY != 0) {
    //    this.logWarn("moveTo: " + [x,y] + " calling moveBy: " + [deltaX, deltaY] +
    //                 ", top: " + this.getTop() + ", scrollTop: " + this.scrollTop +
    //                 ", left: " + this.getLeft() + ", scrollLeft: " + this.scrollLeft +
    //                 ", using CSS scrolling: " + this.usingCSSScrollbars());
    //}

	// ... and call the moveBy function to do it for us
	return this.moveBy(deltaX, deltaY, animating, resizeHandle);
},


//>	@method	canvas.moveToEvent()
//			move to the last event location (such as when we're being dragged around)
//		@group	positioning, events
//		@param	[offsetX]		(number)	x-coordinate offset (typically used for drag and drop)
//		@param	[offsetY]		(number)	y-coordinate offset (typically used for drag and drop)
//<
moveToEvent : function (offsetX, offsetY) {

    // get the global coordinates of the event, maintaining the drag offset
    var event = this.ns.EH.getLastEvent(),
		x = event.x,
		y = event.y
	;

    if (isc.isA.Number(offsetX)) x -= offsetX;
    if (isc.isA.Number(offsetY)) y -= offsetY;
    // Snap-to-grid
    
    var EH = this.ns.EH;
    var snapChild = EH.getDragTarget(event);
    var snapParent;
    if (EH.getDragTarget().canDrop) {
        snapParent = EH.getDropTarget(event);
        if (snapParent) {
            
            if ( ! snapChild.snapOnDrop || ! snapParent.shouldSnapOnDrop(snapChild) ) {
                snapParent = null;  // Effectively switches off snap-to-grid
            }
        } else {
            snapParent = EH.getDragTarget(event).parentElement;
        }
    } else {
        snapParent = EH.getDragTarget(event).parentElement;
    }

    // Parentless canvases cannot participate in snap-to-grid
    if (isc.isA.Canvas(snapParent) &&
        (snapChild.snapToGrid == true ||
            (snapChild.snapToGrid == null && snapParent.childrenSnapToGrid == true)))
    {
        // Support suppressing the drag offset.
        // This is used in GridRenderer where we want the drag child to snap to whatever
        // cell the mouse is regardless of original drag offset
        if (snapParent.noSnapDragOffset(this)) {
            x = event.x,
            y = event.y
        }
        // allow snapOffsets to be individually disabled by axis.
        // useful in calendar.timelineView drag and drop
        if (snapParent.suppressHSnapOffset == true) x = event.x;
        if (snapParent.suppressVSnapOffset == true) y = event.y;

        if (snapParent.snapAxis == isc.Canvas.HORIZONTAL ||
            snapParent.snapAxis == isc.Canvas.BOTH)
        {
            var snapParentContentOffset =
                (snapParent.getPageLeft() + snapParent.getLeftBorderSize() +
                  snapParent.getLeftMargin() - snapParent.getScrollLeft());
            x -= snapParentContentOffset;
            x = snapParent.getHSnapPosition(x) + snapParent.getHSnapOrigin(snapChild);
            x += snapParentContentOffset;
        }
        if (snapParent.snapAxis == isc.Canvas.VERTICAL ||
            snapParent.snapAxis == isc.Canvas.BOTH)
        {
            var snapParentContentOffset =
                 (snapParent.getPageTop() + snapParent.getTopBorderSize() +
                  snapParent.getTopMargin() - snapParent.getScrollTop())
            y -= snapParentContentOffset;
            y = snapParent.getVSnapPosition(y) + snapParent.getVSnapOrigin(snapChild);
            y += snapParentContentOffset;
        }
    }

    // x/y is where we want to move to in global coordinates, so use setPageRect
    // (Don't pass in width and height - will just move to page coordinates)
    this.setPageRect(  x, y );
},

//> @method canvas.getVSnapOrigin()
// Get an offset to be used when calculating snap positioning. Returns 0 by default.
//
// @param [snapChild] (Canvas) the child that is being snapped
// @return (int) The offset to use when snapping
//
// @group positioning
// @see canvas.getVSnapPosition
// @visibility external
//<
getVSnapOrigin : function (snapChild) {
    return this.VSnapOrigin ? this.VSnapOrigin : 0;
},

//> @method canvas.getHSnapOrigin()
// Get an offset to be used when calculating snap positioning. Returns 0 by default.
//
// @param [snapChild] (Canvas) the child that is being snapped
// @return (int) The offset to use when snapping
//
// @group positioning
// @see canvas.getHSnapPosition
// @visibility external
//<
getHSnapOrigin : function (snapChild) {
    return this.HSnapOrigin ? this.HSnapOrigin : 0;
},

//>	@method	canvas.placeNextTo()
//  Move this canvas so that it is directly next to another canvas, unless that would cause
//  this canvas to extend beyond the browser window in some direction, in which case this
//  canvas should be placed such that it doesn't extend beyond the browser viewport.
//      @group	positioning, events
//      @param	otherWidget (Canvas)    Canvas to move next to
//      @param  [side]  (string)    Which side of the other canvas should we put. Options are
//                                  "top", "bottom", "left", "right". (Defaults to "bottom")
//      @param  [canOcclude]    (boolean)
//          This property controls whether this canvas can be positioned on top of the other
//          widget if there isn't room to put it next to the other widget without going off
//          screen.<br>
//          If 'canOcclude' is true, simply shift this widget over the other widget, so that
//          it ends up onscreen.  If 'canOcclude' is false, avoid extending offscreen
//          by positioning this widget on the other side of the other widget.
//      @param  [otherAxisAlign]    (string)    Can be one of "left", "right", "outside-left",
//                                          "outside-right", "top", "bottom", "outside-top",
//                                          "outside-bottom". (Defaults to "left" if side is
//                                          "top" or "bottom", "top" if side is "left" or
//                                          "right").<br>
//                                          This property determines how this widget will be
//                                          aligned with the other widget on the other axis.
//<
placeNextTo : function (otherWidget, side, canOcclude, otherAxisAlign) {
    // Pick up defaults for side, canOcclude, otherAxisAlign from _placeRect
    var adjacentRect = otherWidget.getPeerRect(),
        thisRect = this.getPeerRect(),
        pos = isc.Canvas._placeRect(
                thisRect[2], thisRect[3],
                adjacentRect, side, canOcclude, otherAxisAlign
              )
    ;

    this.setPageRect(pos[0], pos[1]);
},

//> @method canvas.showNextTo()
// Show this widget next to another widget on the page, positioned such that it will 
// not extend beyond the browser viewport.
// <P>
// Note that this method simply sets the coordinates of the widget and displays it (using
// a +link{canvas.animateShow()} by default). It will
// not change the +link{canvas.parentElement} of either component.
// <P>
// An example use case might be showing a menu next to a menu-button.
//
// @param otherWidget (Canvas) Canvas to show next to
// @param [side] (String) which side to show on, defaults to "right"
// @param [canOcclude] (boolean) 
//  This argument controls whether this canvas can be positioned on top of the other
//  widget if there isn't room to put it next to the other widget extending out of the
//  browser viewport<br>
//  If 'canOcclude' is true, simply shift this widget over the other widget, so that
//  it ends up onscreen.  If 'canOcclude' is false, avoid extending offscreen
//  by positioning this widget on the other side of the other widget.
// @param [skipAnimation] (boolean) If <code>false</code> do not use an animation to
//  show the component.
//
// @visibility external
//<
showNextTo : function (otherWidget, side, canOcclude, skipAnimation) {
    if (side == null) side = "right";
    if (canOcclude == null) canOcclude = false;
    this.placeNextTo(otherWidget, side, canOcclude);
    if (skipAnimation) {
        // For top level widgets, "show()" also draws
        // Not so for widgets embedded in an already drawn parent - explicitly
        // call draw() as well as show so we pop up on the page in either case.
        if (!this.parentElement || this.parentElement.isDrawn()) {
            this.draw();
        }
        if (!this.isVisible()) this.show();
        
    } else {
    this.animateShow("fade");
    }
},

//>	@method	canvas.placeNear()
//  Move this canvas to the specified point, or as close to the specified point as possible
//  without this widget extending beyond the edge of the browser viewport on any side.
//      @group	positioning, events
//      @param	[left]  Left coordinate (defaults to mouse position)
//      @param  [top]   Top coordinate  (defaults to mouse position)
//<
placeNear : function (left, top) {
    if (isc.isAn.Array(left)) {
        top = left[1]; left = left[0];
    } else if (isc.isAn.Object(left)) {
        top = left.top; left = left.left;
    }

    // If we're currently hidden, the shadow will be sized small and offscreen
    // force it to fit to master before measuring the peer-rect so we dont shove it offscreen
    // and generate unnecessary scrollbars   
    if (this.showShadow && this._shadow) this.updateShadow();
    var thisRect = this.getPeerRect(),
        pos = isc.Canvas._placeRect(
                    thisRect[2], thisRect[3], {left:left, top:top}
              );
    this.setPageRect(pos[0], pos[1]);
},



// Resizing
// --------------------------------------------------------------------------------------------

//>	@method	canvas.resizeBy()   ([])
//			Resizes the widget, adding deltaX to its width and deltaY to its height (moves the right
//          and/or bottom sides of the widget).
//		@group	sizing
//		@param	[deltaX]	(number)	amount to resize horizontally (may be negative)
//		@param	[deltaY]	(number)	amount to resize vertically (may be negative)
//		@return	(boolean)	whether the component actually changed size
//      @visibility external
//      @example    resize
//<
// @param [animating] (boolean) Internal optional parameter indicating that this resize is
//  occurring as part of an animation
// @param [suppressHandleUpdate] (boolean) If passed avoid actually updating the handle
resizeBy : function (deltaX, deltaY, animating, suppressHandleUpdate, reason) {
    if (isc._traceMarkers) arguments.__this = this;

    //>Animation
    // If an external resizeBy is called during an animated setRect, finish the animated setRect
    // before starting the explicit resize.
    // Note: setRect will pass the suppressHandleUpdate param
    var setRectAnimating = animating && suppressHandleUpdate;
    if (!setRectAnimating && this.rectAnimation) this.finishAnimation("rect");
    if (!animating) {
        // If we're doing a setRect animation, kill any running resizeAnimation
        if (setRectAnimating && this.resizeAnimation) this.finishAnimation("resize");
        // animated show / hide also do a resize.
        if (this.hideAnimation) this.finishAnimation("hide");
        if (this.showAnimation) this.finishAnimation("show");
    }
    //<Animation

    

    var oldWidth = this.getWidth(), oldHeight = this.getHeight();
	// adjust width and height by the values passed in
    
	if (isc.isA.Number(deltaX)) {
		this.width += deltaX;
        // set a marker for Layouts (not yet used)
        if (!this._canvas_initializing) this._widthSetAfterInit = true;
	} else {
		deltaX = 0;
    }

	if (isc.isA.Number(deltaY)) {
        
		this.height = this._height = oldHeight + deltaY;
        // set a marker for Layouts (not yet used)
        if (!this._canvas_initializing) this._heightSetAfterInit = true;
	} else {
		deltaY = 0;
    }

    // no-op.  This is very important as generally most Canvii redraw if they are resized, and
    // layout code is very likely to blindly call resizeTo() in no-op situations.
    // NOTE: it's possible to fool a Canvas into not resizing when it needs to resize, by
    // setting the width/height properties directly without calling setters, then calling a
    // move/resize function with the current values, which causes the Canvas to believe there
    // has been no change in size.  This just means that you really have to call the setter
    // functions, as it's critical to be able to no-op here!
    if (deltaX == 0 && deltaY == 0) return false;

    // Store the delta's locally - used by _completeResizeBy
    // - will be cleared out when we actually resize the handle.
    this._resizeDeltaX = deltaX;
    this._resizeDeltaY = deltaY;

    // Also store whether we're animating or not - required by completeResizeBy
    this._resizeAnimating = animating;

    if (this.isDrawn() && this.logIsInfoEnabled(this._$resize)) {
        this.logInfo("resize of drawn component: " +
                     "new width/height: " + [this.width, this._height] +
                     ", old width/height: " + [oldWidth, oldHeight] +
                     ", delta width/height: " + [deltaX, deltaY] +
                     (this.logIsDebugEnabled(this._$resize) ?
                      this.getStackTrace() : ""), this._$resize);
    }

    // we don't fire resized() if suppressHandleUpdate is true - this will be called from from
    // moveBy()
    // This ensures that when resized() is fired the handle has actually been resized.
    
    if (!suppressHandleUpdate) {

        // if we have a clip region set, it will have been clobbered by _setHandleRect.
        // restore it:
        // Note: since we're resizing from the top left (bottom / right will 'move'), adjust
        // those coords of the clip by the amount we've resized.
        var clip = this._clip;
        if (isc.isAn.Array(clip)) {
            clip[1] += deltaX;
            clip[2] += deltaY;
        }

        var drawnState = this.getDrawnState();
        if (drawnState == isc.Canvas.COMPLETE) {
            // actually resize the handle by calling _setHandleRect
            this._setHandleRect(this.left, this.top, this.width, this._height);

            if (isc.isAn.Array(clip)) this.setClip(clip);

        // If we've already got our tag start but haven't finished drawing, when we write it out
        // the handle will be the wrong size.
        // Set a flag so that when we *are* done drawing we resize our handle before adjusting
        // overflow
        } else if (drawnState != isc.Canvas.UNDRAWN) {
            this._resizeHandleOnDrawComplete = true;
        }
        this._completeResizeBy(reason);
    }

	// return true indicating that a resize actually occurred (as opposed to a no-op of staying
    // the same size)
    return true;
},

_$resized: "resized",
_completeResizeBy : function (reason) {
    var deltaX = (this._resizeDeltaX || 0),
        deltaY = (this._resizeDeltaY || 0),
        animating = this._resizeAnimating,
        undef;

    this._resizeDeltaX = undef;
    this._resizeDeltaY = undef;
    this._resizeAnimating = undef;

    // Bail if the delta is zero or null
    
    if (!deltaX && !deltaY) return;

    var redrawOnResize;
    if (this.isDrawn()) {
        // check if we're supposed to redraw on resize
        
        redrawOnResize = this.shouldRedrawOnResize(deltaX, deltaY, animating);
        // if we're supposed to redraw when resized, mark for a redraw
        if (redrawOnResize) {
            //if (this.isDrawn()) {
            //    this.logWarn("redrawing due to resize: " +
            //                 "old width/height: " + [oldWidth, oldHeight] +
            //                 ", delta width/height: " + [deltaX, deltaY]);
            //}
            this.markForRedraw(this._$resize);
        }
    }

	// run layout code to resize children, if any.  Note this needs to happen before we
    // adjustOverflow.
	if (!animating) this.layoutChildren(this._$resized, deltaX, deltaY)

    if ((isc.Browser.isMoz || isc.Browser.isSafari) && this.containsIFrame()) this._sizeIFrame();

    
    this._handleResized(deltaX, deltaY);

	// if we're not going to redraw, which would adjust overflow automatically, we need to adjust
    // now.
    if (!redrawOnResize) this.adjustOverflow(this._$resize);

    //>FocusProxy
    // If we're showing a focus proxy, resize it to match our new (specified) size, so that
    // when the user tabs into the focus proxy, the whole widget gets scrolled into view.
    
    if (!animating && this._useFocusProxy && this._hasFocusProxy) {
        var fp = this._getFocusProxyHandle();
        if (fp != null) {
            fp.style.width = this.getWidth() + isc.px;
            fp.style.height = this.getHeight() + isc.px;
        }
    }
    //<FocusProxy

    // tell our peers to resize as well
    this.resizePeersBy(deltaX, deltaY);
    // call the observable resized method
    this._resized(deltaX, deltaY, reason);
},

shouldRedrawOnResize : function (deltaX, deltaY) {
    var redrawOnResize = this.redrawOnResize;
    if (redrawOnResize == null) {
        

        // this Canvas doesn't need to redraw if..
        redrawOnResize = !(
            // it's a parent with no content
            (this.children != null && this.children.length > 0 &&
             !this.allowContentAndChildren) ||
            // contents are static: getInnerHTML has not been overridden, this.contents has not
            // been set to a function
            (this.getInnerHTML == isc.Canvas._instancePrototype.getInnerHTML &&
             !isc.isA.Function(this.contents)));
    }
    return redrawOnResize;
},

//> @method canvas.dragResizing()
// Returns true if this widget is currently being drag-resized.
//<
dragResizing : function () {
    var EH = isc.EH;
    return (EH.dragging && EH.dragOperation == EH.DRAG_RESIZE && EH.dragTarget==this);
},


// _resized() - calls public, observable resized() method.
_resized : function (deltaX, deltaY, reason) {
    if (isc._traceMarkers) arguments.__this = this;
//!DONTOBFUSCATE  (we want observers to be able to pick up the passed values)

    // rerun snapTo positioning for cases where size affects positioning (eg snapTo:"R")
    if (this.snapTo) this._resolvePercentageSize(true);

    // fire up/down chain parent/child master/peer notifications
    if (this.parentElement) this.parentElement.childResized(this, deltaX, deltaY, reason);
    if (this.masterElement) this.masterElement.peerResized(this, deltaX, deltaY, reason);

    var peers = this.peers;
    if (peers) {
        for (var i = 0; i < peers.length; i++) {
            if (isc.isA.Canvas(peers[i])) peers[i].masterResized(deltaX, deltaY, reason);
        }
    }

    

    //>CornerClips
    // Also checking for this._cornerClips because Canvas.init() calls resizeTo() before corner
    // clips are created.
    if (this.clipCorners && this._cornerClips) {
		var clips = this._cornerClips;
		if (clips.TR) clips.TR.moveBy(deltaX, null);
		if (clips.BL) clips.BL.moveBy(null, deltaY);
		if (clips.BR) clips.BR.moveBy(deltaX, deltaY);
    }
    //<CornerClips

    //>DragScrolling
    // Kill any cached drag-scrolling thresholds resolved from percentages to pixel values -
    // these will have to be recalculated as percentage of the new viewport size. Done lazily
    // on drag scrolling in handleDropMove()
    
    if (this._hDragScrollThreshold != null) delete this._hDragScrollThreshold;
    if (this._vDragScrollThreshold != null) delete this._vDragScrollThreshold;
    //<DragScrolling

    this.resized(deltaX, deltaY, reason);


	// when a top level element is moved or resized it can introduce page level scrollbars, changing
	// the browser window size overall.
	// We don't get a resized event notification from the browser on this, so explicitly run the
	// _pageResize() method
	// Note that childResized() may cause a parent to resize as part of adjustOverflow but in that
	// case '_resized()' is still fired on the parent so no need for an additional check in
	// the childResized() method.
	if (!this._pageResizing && this.isDrawn() && this.parentElement == null
		&& !isc.Page.pollPageSize)
	{
		isc.EH.fireOnPause("checkForBodyOverflowChange",
							{target:isc.Canvas, methodName:"checkForPageResize"},
							100);
    }
},

_handleResized : function () {},

//>	@method   canvas.resized()
//  Observable method called whenever a Canvas changes size. Note that if this canvas is
// +link{canvas.overflow,overflow:"visible"}, and is waiting for a queued redraw (see
// +link{canvas.isDirty()}), the value for +link{canvas.getVisibleWidth()} and
// +link{canvas.getVisibleHeight()} will be unreliable until <code>redraw()</code> fires.
// @visibility external
//<

resized : function (deltaX, deltaY) {},

// Fired when the viewport size changes but not the overall widget size
// Used to resize children and peers with snapTo:true and percent sizing
// Also resizes widgets for which this is the percentSource - handled by observation
innerSizeChanged : function (reason) {

    
    this._childrenCoordsChanged();

    this.layoutChildren(reason);
    var peers = this.peers;
    if (peers) {
        for (var i = 0; i < peers.length; i++) {
            if (!peers[i].percentSource && peers[i].snapTo &&
                peers[i].percentBox == this._$viewport
               )
           {
               peers[i]._resolvePercentageSize();
           }
        }
    }
},

//> @method canvas.setPercentSource() [A]
// Setter method for the +link{canvas.percentSource,percentSource} attribute.
// @parameter [sourceWidget] (Canvas) New percent source (if omitted existing
//                                      percentSource will just be cleared).
// @visibility external
// @group sizing
//<
setPercentSource : function (sourceWidget, initTime) {

    if (isc.isA.String(sourceWidget)) sourceWidget = window[sourceWidget];
    if (!initTime && this.percentSource == sourceWidget) return;

    if (this.percentSource && this.isObserving(this.percentSource, "innerSizeChanged")) {
        this.ignore(this.percentSource, "innerSizeChanged");
        this.ignore(this.percentSource, "resized");
    }

    if (!isc.isA.Canvas(sourceWidget)) {
        this.percentSource = null;
        return;
    }
    this.percentSource = sourceWidget;
    this.observe(sourceWidget, "innerSizeChanged", "observer.percentSourceInnerSizeChanged()");
    this.observe(sourceWidget, "resized", "observer._resolvePercentageSize()");
},

percentSourceInnerSizeChanged : function () {
    if (this.percentBox == this._$viewport) this._resolvePercentageSize();
},

// If an overflow:VISIBLE child resets its handle for adjust overflow
// (IE shrinks to specified size, then resizes to drawn size), it may impact our scroll
// position natively as the scrollWidth/scrollHeight may change.
// Remember the scroll position before this occurs, and then reset to it after adjustOverflow
// finishes resizing the child handle to its final size.

childResettingHandleForAdjustOverflow : function () {
    if (this.overflow == isc.Canvas.VISIBLE) {
        if (this.parentElement) this.parentElement.childResettingHandleForAdjustOverflow();
    } else {
        this._scrollLeftBeforeChildReset = this.getScrollLeft();
        this._scrollTopBeforeChildReset = this.getScrollTop();
    }
},
childResetHandleForAdjustOverflowComplete : function () {
    if (this.overflow != isc.Canvas.VISIBLE) {
        this.scrollTo(this._scrollLeftBeforeChildReset, this._scrollTopBeforeChildReset);
        this._scrollLeftBeforeChildReset = this._scrollTopBeforeChildReset = null;
    }
},

_$childResized : "childResized",
childResized : function (child, deltaX, deltaY, reason) {

    // if a child changes size, the size of our content has changed, so adjustOverflow.  For
    // example, we may need to grow/shrink to fit (overflow:visible), or show or hide scrollbars
    // (overflow:auto).

    
    if (this.allowContentAndChildren && this.overflow == isc.Canvas.VISIBLE)
        this._resetHandleOnAdjustOverflow = true;
    this._markForAdjustOverflow(this._$childResized);
    //this.logWarn("child resize: " + this.getStackTrace());
},

peerResized : function (peer, deltaX, deltaY, reason) { },

masterResized : function (deltaX, deltaY, reason) {
	this._resolvePercentageSize();
},

//> @method canvas.dragResized()    (A)
// Observable function fired once at the end of a successful drag-resize operation.
// Useful for firing some action in response to resize without firing repeatedly on every
// dragMove while the user is drag-resizing the target.
//<
dragResized : function () {},


//>	@method	canvas.resizePeersBy()	(A)
//			resize any peers by the amounts specified, if we have any
//		@group	sizing
//		@param	deltaX		(number)	amount to resize horizontally (may be negative)
//		@param	deltaY		(number)	amount to resize vertically (may be negative)
//<
resizePeersBy : function (deltaX, deltaY) {
    
    var peers = this.peers;
	if (peers) {
		for (var i = 0; i < peers.length; i++) {
		    // Avoid resizing the peer in the case where we haven't yet assigned its
		    // "masterElement" link - this can happen if we were initialized with peers - in this
		    // case we don't want to resize them until we've had time to set up (for example) the
		    // snapToEdge stuff.
			if (peers[i] && peers[i].masterElement == this && peers[i]._resizeWithMaster) {
				peers[i].resizeBy(deltaX, deltaY);
            }
		}
	}
},


//>	@method	canvas.layoutChildren()	([A])
//
// <code>layoutChildren()</code> is where a Canvas should implement a sizing policy for it's
// Canvas children.  Since <code>layoutChildren</code> calls parentResized() on its children,
// +link{Canvas.parentResized} is a good place for a child to implement a layout policy that
// can be used within any parent.
// <P>
// Recommended practice for a Canvas that manages Canvas children is to create those children
// without any initial coordinate or size settings and do all sizing when layoutChildren() is
// called.
// <P>
// layoutChildren() is always called at least once before children are drawn, and is called
// automatically whenever the viewport size changes (which includes both resizing and
// introduction/removal of scrolling).  layoutChildren() can also be manually invoked in any
// other component-specific situation which changes the layout.
// <P>
// NOTE: layoutChildren() may be called before draw() if a widget is resized before draw(), so
// be sure to avoid errors such as assuming that any children you automatically create have
// already been created.
// <P>
// NOTE: auto-sizing: layoutChildren() is also called once during the initial draw(), before
// children are drawn, with a "reason" of "initial draw".  During this invocation of
// layoutChildren() it is legal to directly draw children (call child.draw()), which is
// otherwise never allowed.  This allows a Canvas to implement an auto-sizing layout policy by
// drawing some children before deciding on the sizes of remaining children, which is far more
// efficient than drawing all children and resizing some of them after they are drawn.
// @param reason (string) reason why layoutChildren() is being called, passed when framework
//                        code invokes layoutChildren()
//
//  @visibility external
//	@group	sizing
//<
layoutChildren : function (reason, deltaX, deltaY) {
    if (this.children) this._resolveChildPercentSizes();
},

// tell any percent-size children to update size
_resolveChildPercentSizes : function () {
    var children = this.children;
    if (children != null && children.length > 0) {
        for (var i = 0; i < children.length; i++) {
            if (isc.isA.Canvas(children[i])) children[i].parentResized();
        }
    }
},

//>	@method	canvas.resizeTo()   ([])
//			Resizes the widget to the specified width and height (moves the right and/ or bottom
//          sides of the widget). The width and height parameters can be expressed as a percentage
//          of viewport size or as the number of pixels.
//		@group	sizing
//		@param	[width]		(number)	new width for canvas
//		@param	[height]	(number)	new height for canvas
//      @return (boolean) whether the size actually changed
//      @visibility external
//      @example    resize
//<
// @param [animating] (boolean) optional internal param passed if this is a resize occurring as
// part of an animation
// @param [suppressHandleUpdate] (boolean) If passed avoid actually updating the handle
resizeTo : function (width, height, animating, suppressHandleUpdate, reason) {
    if (isc._traceMarkers) arguments.__this = this;

    if (width == null && height == null) return false;

    var deltaX = this.getDelta(this._$width, width, this.getWidth()),
        deltaY = this.getDelta(this._$height, height, this.getHeight());
	// now call resizeBy to do the work for us
	return this.resizeBy(deltaX, deltaY, animating, suppressHandleUpdate, reason);
},

//>	@method	canvas.resizeToEvent()
//		Resize according to an event, such as when resizing in a drag.
//		Uses isc.EventHandler.lastEvent for the event coordinates.
//
//		@group	sizing, events
//		@param	resizeEdge		(string)	Edge or corner to resize (eg: "T" or "BR", etc).
//<
resizeToEvent : function (resizeEdge) {
	var EH = this.ns.EH,
		event = EH.getLastEvent(),
		x =	event.x,
        y = event.y,
		left = this.getPageLeft(),
		top = this.getPageTop(),
		right = this.getPageRight(),
		bottom = this.getPageBottom();

        // Snap-to-grid - adjust x/y to grid as required, before validity checks
        
        var snapChild = EH.getDragTarget(event);
        var snapParent = EH.getDragTarget(event).parentElement;

        if (snapParent) {
            if (snapChild.snapResizeToGrid == true ||
                (snapChild.snapResizeToGrid == null && snapChild.snapToGrid == true) ||
                (snapChild.snapResizeToGrid == null &&
                    (snapParent.childrenSnapResizeToGrid == true ||
                       (snapParent.childrenSnapResizeToGrid == null &&
                        snapParent.childrenSnapToGrid == true)))) {
                
                if (snapParent.snapAxis == isc.Canvas.HORIZONTAL ||
                    snapParent.snapAxis == isc.Canvas.BOTH) {
                    var snapParentContentOffset =
                        (snapParent.getPageLeft() + snapParent.getLeftBorderSize() +
                          snapParent.getLeftMargin() - snapParent.getScrollLeft());
                    x -= snapParentContentOffset;
                    x = snapParent.getHSnapPosition(x) + snapParent.getHSnapOrigin(snapChild);
                    x += snapParentContentOffset;
                }
                if (snapParent.snapAxis == isc.Canvas.VERTICAL ||
                    snapParent.snapAxis == isc.Canvas.BOTH) {
                    snapParentContentOffset =
                        (snapParent.getPageTop() + snapParent.getTopBorderSize() +
                          snapParent.getTopMargin() - snapParent.getScrollTop());
                    y -= snapParentContentOffset;
                    y = snapParent.getVSnapPosition(y) + snapParent.getVSnapOrigin(snapChild);
                    y += snapParentContentOffset;
                }
            }
        }

    //>DEBUG
    if (this.logIsDebugEnabled("dragResize")) {
        this.logDebug("resizeToEvent: coords: " +
                      isc.Log.echo({x:x, y:y, left:left, top:top, right:right, bottom:bottom}),
                      "dragResize");
    } //<DEBUG

	resizeEdge = resizeEdge || EH.resizeEdge || "BR";

    // for each side participating in the resize, figure out how much it should change,
    // refusing to resize beyond the min/max height and width.

    // top or bottom
	if (resizeEdge.contains("T")) {
		var height = Math.min(this.maxHeight, Math.max(bottom - y, this.minHeight));
		top = bottom - height;
	} else if (resizeEdge.contains("B")) {
		var height = Math.min(this.maxHeight, Math.max(y - top, this.minHeight));
		bottom = top + height;
	}

    // left or right
	if (resizeEdge.contains("L")) {
		var width = Math.min(this.maxWidth, Math.max(right - x, this.minWidth));
		left = right - width;
	} else if (resizeEdge.contains("R")) {
		var width = Math.min(this.maxWidth, Math.max(x - left, this.minWidth));
		right = left + width;
	}

    var newWidth = right - left,
        newHeight = bottom - top;

    this.setPageRect(left, top, newWidth, newHeight, true);

	// set EH.dragResizeWidth and EH.dragResizeHeight
	//	so other routines can know how big the resizing thing will be
	EH.dragResizeWidth = newWidth;
	EH.dragResizeHeight = newHeight;

	// HACK: if we're resizing the dragTracker, redraw immediately, this looks MUCH cleaner
	if (this == this.ns.EH.dragTracker) this.redrawIfDirty();
},

// Generic interaction for resizing some target widget as we are moved
// ---------------------------------------------------------------------------------------

resizeTarget : function (target, vertical, realTime, offset, ignore, coord, targetAfter) {
    // ignore: number of pixels between targetCoord and coord to ignore for the purposes of
    // this calculation (used for skipping intervening collapsed headers in SectionStack)
    ignore = ignore || 0;
    // offset: drag offset
    offset = offset || 0;

    if (coord == null) coord = vertical ? isc.EH.getY() : isc.EH.getX();
    coord += offset;

    // don't allow to drag the target past the edge of the parent
    // Essentially what we're doing here is keeping the drag-widget (EG splitBar) inside
    // the parent rect
    if (this.parentElement) {
        var parentRect = this.getParentPageRect(),
            maxCoord = vertical ? (parentRect[1] + parentRect[3])
                                : (parentRect[0] + parentRect[2]);
        maxCoord -= vertical ? this.getVisibleHeight() : this.getVisibleWidth();
        if (coord > maxCoord) coord = maxCoord;
    }

    targetAfter = targetAfter != null ? targetAfter : !vertical && this.isRTL();

    var min = vertical ? target.getMinHeight() : target.getMinWidth(),
        max = vertical ? target.getMaxHeight() : target.getMaxWidth();

    var targetCoord;
    if (targetAfter) {
        targetCoord = (vertical ? target.getPageBottom() : target.getPageRight())
            // adjust by the resizeBar's thickness since newSize is determined by
            // the right coordinate of the target as compared to the right coordinate of the
            // resizeBar
            - (vertical ? this.getVisibleHeight() : this.getVisibleWidth());
    } else {
        targetCoord = vertical ? target.getPageTop() : target.getPageLeft();
    }


    // determine size implied by the resizeBar's current position
    var newSize = !targetAfter ?
                            // target before us: target is to our left (or top),
                            // newSize is our coord - target's left
                            coord - targetCoord - ignore :
                            // target after us: target is to our right (or bottom),
                            // newSize is target's right - our coord
                            targetCoord - coord - ignore;


    // clamp size to max min
    if (newSize < min) {
        newSize = min;
    } else if (newSize > max) {
        newSize = max;
    }
    // save off targetSize for finishTargetResize()
    this._targetSize = newSize;

    // calculate where the resizeBar should be
    coord = targetCoord + ignore + (targetAfter ? - newSize : newSize);

    if (realTime) {
        // resize the target
        vertical ? target.setHeight(this._targetSize) : target.setWidth(this._targetSize);
    } else {
        // just move this widget
        vertical ? this.setPageTop(coord) : this.setPageLeft(coord);
    }
},

finishTargetResize : function (target, vertical, realTime) {
    if (realTime) return;
    vertical ? target.setHeight(this._targetSize) : target.setWidth(this._targetSize);
},

// ---------------------------------------------------------------------------------------

//> @method canvas.parentResized()
// Fires when the interior size of the parent changes, including parent resize and
// scrollbar introduction or removal.
// <p>
// This method allows a child to implement a layout policy that can be used within any
// parent, such as a Resizer component that always snaps to the parent's
// bottom-right corner.  The default implementation of this method applies a child's
// percent sizes, if any, or implements layout based on the +link{Canvas.snapTo} property
// @group sizing
// @visibility external
//<
parentResized : function () {
    if (isc._traceMarkers) arguments.__this = this;
    this._resolvePercentageSize();
},

// called on an individual child to tell it to resolve it's own percent sizes and/or snapTo
// coordinates.
_resolvePercentageSize : function (positionOnly) {
    // percentBox:"custom" -- assume the percentage sizing / positioning will be explicitly
    // managed by some custom logic
    if (this.snapTo != null && this.percentBox != "custom") {
        // if the child has percent size, need to resize first so that centering logic is correct
        if ((this._percent_width || this._percent_height) && !positionOnly) {
            this.resizeTo(this._percent_width, this._percent_height);
        }
        var target, targetOrigin, insideCoords;
        target = (this.masterElement ? this.masterElement : this.parentElement);
        if (!target) return; // use this info later to implement snapTo page

        isc.Canvas.snapToEdge(target, this.snapTo, this, this.snapEdge);

    }
    // if snapTo was invalid or really is null
    if (this.snapTo == null && !positionOnly) {
        if (this._percent_left || this._percent_top ||
            this._percent_width || this._percent_height)
        {
            this.setRect(this._percent_left, this._percent_top,
                         this._percent_width, this._percent_height);
        }
    }
},

prepareForDragging : function () {
    var EH = this.ns.EH;

    // this would indicate that a child has set itself as the dragTarget, and then
    // prepareForDragging bubbled to this Canvas.  By default, we leave this alone.
    if (EH.dragTarget) return;

    // NOTE: interesting case:
    // - a parent that wants to be drag resizable may have children which are flush with the
    //   parent's edges.  If those children are themselves resizable they will have set
    //   themselves as the dragTarget.  The parent may want to override this.

    var isDraggable = false,
    	dragOperation = this.dragOperation;

    //>Touch
    if (isc.Browser.isTouch && this.touchDragOperation &&
        EH.lastEvent.originalType == EH.TOUCH_START)
    {
        // touch-specific drag operation overrides for touch interfaces
        dragOperation = this.touchDragOperation;
    }
    //<Touch

    // use explicit drag operation setting
    if (dragOperation) {
		isDraggable = true;
		EH.dragOperation = dragOperation;

	// if the target can be resized by dragging,
	} else if (this.canDragResize) {
		// see if the cursor is over an edge where this Canvas can be resized
		EH.resizeEdge = this.getEventEdge();

		if (EH.resizeEdge) {
			// built-in drag resizing:
            // - EventHandler will automatically show a resize animation according to
            //   this.dragAppearance, and will permanently resize this Canvas on mouseUp
            // - this Canvas will receive dragResizeStart/Move/Stop events, which bubble to
            //   parents
			isDraggable = true;
			EH.dragOperation = EH.DRAG_RESIZE;
			// if drag appearance is 'tracker', don't resize the tracker to fit the
			// drag operation, just move it as we would with a dragReposition
			var dragResizeAppearance = this.getDragAppearance(EH.DRAG_RESIZE);
			EH.dragMoveAction = (dragResizeAppearance == "tracker") ? EH._moveDragMoveTarget
			                    : EH._resizeDragMoveTarget;
		}
	}
	if (!isDraggable) { // not a drag resize..
		if (this.canDragReposition) {
            // built-in drag repositioning
			// - EventHandler will automatically show a move animation according to
            //   this.dragAppearance, and will permanently reposition this Canvas on mouseUp
            // - this Canvas will receive dragRepositionStart/Move/Stop events, which bubble to
            //   parents
			isDraggable = true;
			EH.dragOperation = EH.DRAG_REPOSITION;
			EH.dragMoveAction = EH._moveDragMoveTarget;

        //>Touch In touch interfaces, default scrollable regions to drag scrolling if no
        // specific drag flags have been set.  This means that eg a ListGrid will scroll by
        // default, and will need to show drag handles on records or a similar UI to
        // offer normal drag modes.
        } else if (isc.Browser.isTouch && (this.hscrollOn || this.vscrollOn) &&
                   !this.dragOperation)
        {
            // built-in drag scrolling
            // - target will receive dragScrollStart et al, which are implemented on Canvas
            isDraggable = true;
			EH.dragOperation = EH.DRAG_SCROLL;
			// no need to set dragAppearance - we'll handle that in EH
        //<Touch

		} else if (this.canDrag) {
			// generic drag interaction:
            // - EventHandler will show a move animation according to this.dragAppearance
            // - this Canvas will receive dragStart/Move/Stop events, which bubble to parents
			isDraggable = true;
			EH.dragOperation = EH.DRAG;

        // allow drag-scroll with text selection
        // If some other canDrag property is set check that first - that will take
        // precedence over drag-text-selection behavior
        } else if (this.canSelectText) {
            isDraggable = true;
			EH.dragOperation = EH.DRAG_SELECT;
			this.dragAppearance = "none";
		}

	}

	// This canvas can designate another Canvas as the object that should be dragged (via the
    // dragTarget property).  This is to support situations where some external widget (such as
    // resize nubs) drives drag and drop behavior for another Canvas.
    // NOTE: dragRelated properties (EH.dragOperation etc) are derived from properties
    // (canDragReposition, etc) on the mouse event target. The delegated dragTarget may not have
    // these drag-related properties set, but will recieve the actual dragRepositionStart etc events.
    // [Example use-case - Window is marked as canDragReposition:true which prevents it being dragged
    // directly - but the label is canDragReposition:true, with dragTarget set causing dragging on
    // the label to issue dragReposition events on the Window]
    if (isDraggable) {
        var dragTarget = this;
        
        if (EH.dragOperation != EH.DRAG_SELECT && this.dragTarget != null) {
            // if it's a canvas, use it
            if (isc.isA.Canvas(this.dragTarget)) {
                dragTarget = this.dragTarget;
            // if it's the constant 'top' and the target has a topElement, use that
            } else if (this.dragTarget == "top" && this.topElement) {
                dragTarget = this.topElement;
            // if it's the constant 'parent' and the target has a parentElement, use that
            } else if (this.dragTarget == "parent" && this.parentElement)   {
                dragTarget = this.parentElement;
            } else if (this.dragTarget == "creator" && this.creator) {
                dragTarget = this.creator;
            } else if (isc.isA.String(this.dragTarget) &&
                        isc.isA.Canvas(window[this.dragTarget]))
            {
                dragTarget = window[this.dragTarget];
            //>DEBUG
            // otherwise we don't know what to do with it...
            } else {
                this.logWarn('prepareForDragging():  target.dragTarget not understood : ' +
                             this.dragTarget);
            //<DEBUG
            }
        }
        EH.dragTarget = dragTarget;
    }
    // not draggable (all 3 flags false: canDrag, canDragResize, canDragReposition, and
    // not selecting text), so don't
    // set a dragTarget.  NOTE: allow this event to bubble, so parents can override our drag
    // settings.
},

//> @method  Canvas.setDragTracker()
// If +link{canvas.dragAppearance} is set to <code>"tracker"</code>, this method will be called
// (if defined), when the user starts to drag this widget. It is an opportunity to update the
// drag tracker to display something relative to this canvas.  Typical implementation will
// be to call +link{EventHandler.setDragTracker()}, passing in the desired custom tracker HTML
// as a string
// @return  (boolean) Return false to suppress bubbling, and prevent <code>setDragTracker()</code>
//                      from being called on this widget's ancestors.
// @group dragdrop
// @visibility external
// @example dragTracker
//<

// Drag Scrolling
// ---------------------------------------------------------------------------------------
// While the mouse is actually down, the region being scrolled moves 1 to 1 with the movement
// of the mouse.  When the mouse is lifted, "momentum" is calculated and an animation is
// kicked off to continue scrolling.
dragScrollStart : function () {
    // allow a settable target for scrolling, since in eg ListGrid, the ListGrid gets the
    // events but the body is what scrolls
    var dragScrollTarget = this.dragScrollTarget || this;

    // start coordinate of mouse
    this._touchStartX = isc.EH.getX();
    this._touchStartY = isc.EH.getY();
    // start scroll position
    this._scrollStartLeft = dragScrollTarget.scrollLeft || 0;
    this._scrollStartTop = dragScrollTarget.scrollTop || 0;

    // init variables we'll use to detect speed
    this._scrollPriorX = this._scrollLastX = isc.EH.getX();
    this._scrollPriorY = this._scrollLastY = isc.EH.getY();
    this._scrollPriorTS = this._scrollLastTS = isc.timestamp();
},

dragScrollMove : function () {
    var dragScrollTarget = this.dragScrollTarget || this;

    // note: we're "grabbing" the content.  Moving the mouse downward scrolls up.
    var leftDelta = this._touchStartX - isc.EH.getX(),
        topDelta = this._touchStartY - isc.EH.getY();

    //isc.logWarn("scrollStart: " + [this.scrollStartLeft, this.scrollStartTop] +
    //            ", scroll delta: " + [leftDelta, topDelta]);

    // note scrollTo automatically clamps to max
    dragScrollTarget.scrollTo(this._scrollStartLeft + leftDelta,
                              this._scrollStartTop + topDelta,
                              "dragScrollMove");
    if (window.event) window.event.preventDefault();

    
    this._scrollPriorX = this._scrollLastX;
    this._scrollPriorY = this._scrollLastY;
    this._scrollPriorTS = this._scrollLastTS;
    this._scrollLastX = isc.EH.getX();
    this._scrollLastY = isc.EH.getY();
    this._scrollLastTS = isc.timestamp();
},

momentumScrolling: true,
// time to stop scrolling in milliseconds.

momentumScrollTime: 1500,
// meaning in this case: slows slowly, then quickly comes to a stop
momentumScrollAcceleration: "smoothStart",

dragScrollStop : function () {
    if (!this.momentumScrolling) return;

    
    var elapsed = (this._scrollLastTS - this._scrollPriorTS);

    // we went directly from scrollStart to scrollStop with no scrollMove.  No momentum.
    if (elapsed == 0) return;

    // no move events in the last 100ms, implying motion stopped.  No momentum
    if (isc.timestamp() - this._scrollLastTS > 100) return;

        // speeds in pixels / ms
    var speedX = (this._scrollLastX - this._scrollPriorX) / elapsed,
        speedY = (this._scrollLastY - this._scrollPriorY) / elapsed,
        target = this,
        dragScrollTarget = this.dragScrollTarget || this;

    if (!dragScrollTarget.hscrollOn) speedX = 0;
    if (!dragScrollTarget.vscrollOn) speedY = 0;

    if (this.logIsDebugEnabled("dragScroll")) {
        this.logDebug("dragScroll: x/y: " + [this._scrollLastX, this._scrollLastY] +
                     ", last: " + [this._scrollPriorX, this._scrollPriorY] +
                     ", elapsed: " + elapsed + ", speed: " + [speedX, speedY], "dragScroll");
    }

    // if there's no speed in any direction in which scrolling is allowed, exit
    if (speedX == 0 && speedY == 0) return;

    // record the animation id since a new mouseDown should instantly stop scrolling
    var animationId = this._momentumScrollId = this.registerAnimation(function (ratio) {
        var now = isc.timestamp(),
            elapsed = now - target._scrollLastTS;
        target._scrollLastTS = now;

        var frameSpeedX = speedX * (1 - ratio),
            frameSpeedY = speedY * (1 - ratio);

        var distanceX = Math.round(frameSpeedX * elapsed),
            distanceY = Math.round(frameSpeedY * elapsed);

        if (this.logIsDebugEnabled("dragScroll")) {
            this.logDebug("animating: elapsed: " + elapsed +
                          ", frame speed: " + [frameSpeedX, frameSpeedY] +
                          ", distance: " + [distanceX, distanceY], "dragScroll");
        }

        if (distanceX == 0 && distanceY == 0) target.cancelAnimation(animationId);

        var oldScrollLeft = dragScrollTarget.getScrollLeft(),
            oldScrollTop = dragScrollTarget.getScrollTop();

        dragScrollTarget.scrollTo(dragScrollTarget.getScrollLeft() - distanceX,
                                  dragScrollTarget.getScrollTop() - distanceY,
                                  "dragScrollStop");

        // cancel if we're out of scrollable content
        if (oldScrollLeft == dragScrollTarget.getScrollLeft() &&
            oldScrollTop == dragScrollTarget.getScrollTop())
        {
            target.cancelAnimation(animationId);
        }


    },
    this.momentumScrollTime,
    this.momentumScrollAcceleration);

},

// Hoop Selection
// ---------------------------------------------------------------------------------------
hoopSelectorDefaults : {
    _constructor:"Canvas",
    keepInParentRect: true,
    redrawOnResize:false,
    overflow: "hidden",
    border: "1px solid blue",
    opacity:10,
    backgroundColor:"blue"
},

// "both", "vertical" or "horizontal".  The hoop fills all vertical space if only horizontal
// hoopSelection is desired, and vice-versa
hoopSelectAxis:"both",

hoopSelectStart : function () {
    if (!this.hoopSelector) this.hoopSelector = this.createAutoChild("hoopSelector");

    //this.logWarn("hoop rect: " + this.echoAll(this.hoopSelectorRect));

    // allows the hoop to be confined to a particular rect
    if (this.hoopSelectorRect) this.hoopSelector.keepInParentRect = this.hoopSelectorRect;

    // always confine to the parent rect or a more specific rect
    var parentRect = this._hoopParentRect = this.hoopSelectorRect ||
            [this.getPageLeft() + this.getLeftBorderSize(),
             this.getPageTop() + this.getTopBorderSize(),
             this.getViewportWidth(),
             this.getViewportHeight()];

    // these are used as flags as well as carrying the full height/width of the selection area
    this._hoopFullHeight = this.hoopSelectAxis == "horizontal" ? parentRect[3] : null;
    this._hoopFullWidth = this.hoopSelectAxis == "vertical" ? parentRect[2] : null;

    //this.logWarn("parentRect: " + this.echoAll(parentRect) +
    //             ", full height, width: " + [ this._hoopFullHeight, this._hoopFullWidth ]);

    this._hoopStartX = this.getOffsetX();
    this._hoopStartY = this.getOffsetY();
    this.resizeHoopSelector();

    this.hoopSelector.show();
    return isc.EH.STOP_BUBBLING; // prevent the parent also doing this drag
},
hoopSelectMove : function () {
    this.resizeHoopSelector();
},
hoopSelectStop : function () {
    if (this.hoopSelector) this.hoopSelector.hide();
},

// resize selector to current mouse coordinates
resizeHoopSelector : function () {
    if (!this.hoopSelector) return;

    var x = this.getOffsetX(),
        y = this.getOffsetY();

    if (this.hoopSelector.keepInParentRect) {
        if (x < 0) x = 0;
        var parentHeight = this._hoopParentRect[3];
        if (y > parentHeight) y = parentHeight;
    }

    // resize to the distances from the start coordinates
    var height = Math.max(1, this._hoopFullHeight ? this._hoopFullHeight :
                             Math.abs(y-this._hoopStartY));
    var width = Math.max(1, this._hoopFullWidth ? this._hoopFullWidth :
                            Math.abs(x-this._hoopStartX));

    this.hoopSelector.resizeTo(width, height);

    // if we are above/left of the origin set top/left to current mouse coordinates,
    // otherwise to start coordinates.
    if (!this._hoopFullWidth) {
        if (x < this._hoopStartX) this.hoopSelector.setLeft(x);
        else this.hoopSelector.setLeft(this._hoopStartX);
    } else {
        this.hoopSelector.setLeft(this._hoopParentRect[0]);
    }

    if (!this._hoopFullHeight) {
        if (y < this._hoopStartY) this.hoopSelector.setTop(y);
        else this.hoopSelector.setTop(this._hoopStartY);
    } else {
        this.hoopSelector.setTop(this._hoopParentRect[1]);
    }

    // figure out which components are now in the selector hoop
    this.updateHoopSelection();
},

// stub to be implemented by classes that enable this drag operation
updateHoopSelection : function () {
},


// Drop Indicator
// ---------------------------------------------------------------------------------------
// If a widget expressly disallows drop in some cases, we want to indicate this with a
// no-drop cursor.
// Example use case: Disallowing drop in certain tree-grid nodes.

//> @method Canvas.setNoDropIndicator()
// Display a "not-allowed" cursor when the user drags over this canvas.
// If +link{Canvas.shouldSetNoDropTracker} is <code>true</code> will also replace the current
// drag tracker (if visible) with the +link{Canvas.noDropTracker} image.
// @see Canvas.clearNoDropIndicator()
// @see Canvas.shouldSetNoDropTracker
//<
setNoDropIndicator : function () {

    this._noDropIndicatorSet = true;

    // The actual 'not allowed cursor' will be picked up by getCurrentCursor()
    // This way we don't have to remember the previous cursor and reset to it on
    // clearNoDropIndicator()
    this._updateCursor();

    // If we should show the no-drop tracker image, and the drag-tracker is showing, do this
    // now.
    
    if (this.shouldSetNoDropTracker && isc.EH.dragTracker && isc.EH.dragTracker.isVisible()) {
        // Remember the current dragTracker content so we can clear if need be
        
        if (!this._activeDragTracker) this._activeDragTracker = isc.EH.dragTracker.getContents();
        isc.EH.setDragTracker(this.imgHTML(this.noDropTracker));
    }
},

//>@method Canvas.clearNoDropIndicator()
// Stop displaying the "not-allowed" cursor (and special no-drop tracker if appropriate)
// while the user drags over this canvas.
// @see Canvas.setNoDropIndicator()
//<
clearNoDropIndicator : function () {

    if (!this._noDropIndicatorSet) return;
    delete this._noDropIndicatorSet;
    this._updateCursor();

    if (this.shouldSetNoDropTracker && isc.EH.dragTracker) {
        isc.EH.setDragTracker(this._activeDragTracker);
        delete this._activeDragTracker;
    }
},

//> @attr canvas.shouldSetNoDropTracker (boolean : varies by browser : [IRWA])
// When +link{Canvas.setNoDropIndicator()} is called, should we replace the current drag-tracker
// with the +link{canvas.noDropTracker} image?<br>
// By default this property is set to true in Opera only as Safari, Moz and IE all support a native
// <code>"not-allowed"</code> cursor.
// @see canvas.setNoDropIndicator()
//<
// Unsupported last tested on Opera version 9.27
shouldSetNoDropTracker : isc.Browser.isOpera,

//> @attr   Canvas.noDropTracker    (SCImgURL : "[SKIN]/shared/no_drop.png" : [IRWA])
// Image to display as the 'no-drop' drag tracker when +link{shouldSetNoDropTracker} is true
// @see canvas.shouldSetNoDropTracker
//<
noDropTracker:"[SKIN]/shared/no_drop.png",


//>DragScrolling
// When a user is dragging a dragTarget widget over a scrollable widget which will accept drop
// we automatically scroll the canAcceptDrop widget when the mousepointer is close to the
// edge of the viewport.
// Can be disabled by setting 'canDragScroll' to false.

//>	@method	canvas.shouldDragScroll() [A]
// If this widget is showing scrollbars, and a user drags close to the edge of the viewport,
// should we scroll the viewport in the appropriate direction?
// Returns this.canDragScroll if there are scrollbars, else false.
// @group events
// @group dragdrop
// @visibility external
//<
shouldDragScroll : function () {
    return this.canDragScroll && (this.hscrollOn || this.vscrollOn);
},

// Determine whether the last event occurred in either the top or bottom scroll thresholds
// Returns -1 if the event occurred in the top scroll threshold, so we should scroll up, and
// +1 if the event occurred in the bottom threshold, so we should scroll down (or zero if not
// over either threshold).
_getVDragScrollDirection : function (offsetY) {

    // resolve the max/min scroll increments (typically specified as percentages)
    // to numeric values
    var vDragThreshold = this.getVDragScrollThreshold();

    if (offsetY < vDragThreshold) return -1;
    if (offsetY > (this.getViewportHeight() - vDragThreshold)) return 1;
    return 0;
},

// Determine whether the last event occurred in either the left or right scroll thresholds
_getHDragScrollDirection : function (offsetX) {
    var hDragThreshold = this.getHDragScrollThreshold();

    if (offsetX < hDragThreshold) return -1;
    if (offsetX > (this.getViewportWidth() - hDragThreshold)) return 1;
    return 0;
},

// Determine whether the last event occurred over any drag scroll threshold
_overDragThreshold : function (direction) {
    var offsetY = (this.getOffsetY() - this.getScrollTop()),
        offsetX = (this.getOffsetX() - this.getScrollLeft());

    if (direction != null) {
        if (direction == isc.Canvas.VERTICAL)
            return this._getVDragScrollDirection(offsetY) != 0;
        else
            return this._getHDragScrollDirection(offsetX) != 0;
    }

    return (this._getVDragScrollDirection(offsetY) != 0 ||
            this._getHDragScrollDirection(offsetX) != 0);
},

// getHDragScrollThreshold() / getVDragScrollThreshold() - method to determine the size of the
// dragScrollThreshold on either axis.
// By default will return this.dragScrollThreshold, resolved from a percentage size if
// necessary.
getHDragScrollThreshold : function () {
    // We cache the values to avoid recalculating from percentage size on every mouseMove.
    // These cached values are dropped on widget resize.
    if (this._hDragScrollThreshold != null) return this._hDragScrollThreshold;
    var tH = this.dragScrollThreshold;
    if (isc.isA.Number(tH)) this._hDragScrollThreshold = tH;
    else {
        // assume it's a percentage
        tH = parseInt(tH);
        if (!isNaN(tH)) {
            this._hDragScrollThreshold = parseInt(tH * this.getViewportWidth() / 100);
            return this._hDragScrollThreshold;
        } else {
            //>DEBUG
            isc.Log.logWarn("Unable to resolve specified drag scroll threshold '" +
                            this.dragScrollThreshold + "' to a valid size. Should be specified as" +
                            " an absolute pixel value, or a percentage of widget viewport.");
            //<DEBUG
            return 0;
        }
    }
},
getVDragScrollThreshold : function () {
    if (this._vDragScrollThreshold != null) return this._vDragScrollThreshold;
    var tH = this.dragScrollThreshold;
    if (isc.isA.Number(tH)) this._vDragScrollThreshold = tH;
    else {
        // assume it's a percentage
        tH = parseInt(tH);
        if (!isNaN(tH)) {
            this._vDragScrollThreshold = parseInt(tH * this.getViewportHeight() / 100);
            return this._vDragScrollThreshold;
        } else {
            //>DEBUG
            isc.Log.logWarn("Unable to resolve specified drag scroll threshold '" +
                            this.dragScrollThreshold + "' to a valid size. Should be specified as" +
                            " an absolute pixel value, or a percentage of widget viewport.");
            //<DEBUG
            return 0;
        }
    }
},

// setupDragScroll
// - If the user is drag-hovering close to the ends of the widget, setup a timer event to start
//   scrolling in the appropriate direction.
_setupDragScroll : function (direction, isDragSelect) {

    // If we're already waiting to scroll no-op
    if (this._dragScrollTimer != null) return;

    var offsetY = (this.getOffsetY() - this.getScrollTop()),
        offsetX = (this.getOffsetX() - this.getScrollLeft()),
        horizontal = this._getHDragScrollDirection(offsetX),
        vertical = this._getVDragScrollDirection(offsetY);

    this._dragScrollTimer =
        isc.Timer.setTimeout({target:this, methodName:"_performDragScroll",
                              args:[horizontal,vertical,true, direction,isDragSelect]},
                              this.dragScrollDelay
                            );
},

// performDragScroll
// Actually scroll the widget in the appropriate direction in response to the user
// drag-hovering close to the edge of the viewport.
// This method is always fired in response to a timer event, set up from either:
// - _setupDragScroll() called by event handler code when the user is dragging over the edge
//   of this widget. In this case we're passed an intended direction of scroll, and the
//   boolean 'firstScroll' parameter. We use these params to avoid the possibility of the
//   user hovering on one side of the widget long enough to start the drag-scroll timer, then
//   moving to a different side, and having scrolling beging before the user has drag-hovered
//   for the requisite length of time on that other side.
// or:
// - _performDragScroll() will setup a timer to call itself, in order to continuously scroll
//  as long as the user hovers over a scroll threshold on the widget.
//
// isDragSelect parameter - passed if a widget is canSelectText:true and the user is
// dragging the mouse outside the drag target widget.
// In this case we want to scroll as long as the mouse is down, and *outside* the widget
_performDragScroll : function (horizontal, vertical, firstScroll, direction, isDragSelect) {
    this._dragScrollTimer = null;

    var hScrollIncrement = 0, vScrollIncrement = 0;
    var containsEvent = this.containsEvent();

    if (this.ns.EH.dragging && (isDragSelect || containsEvent)) {

        var offsetX = this.getOffsetX() - this.getScrollLeft(),
            offsetY = this.getOffsetY() - this.getScrollTop(),
            viewportWidth = this.getViewportWidth(),
            viewportHeight = this.getViewportHeight();

        // the scroll increments may have been set up as percentage values.
        // If so resolve these to pixel values, and cache them for the next _performDragScroll()
        // call.
        // Note that if the user has moved the mouse outside the scroll area when this method fires
        // we'll clear these cached values, so they should never become out of date due to the
        // widget's scrollWidth, etc. changing.
        if (!isc.isA.Number(this.maxDragScrollIncrement)) {
            // resolve percentages
            var maxInc = parseInt(this.maxDragScrollIncrement);
            if (!isc.isA.Number(maxInc))
                this.logWarn("Unable to resolve this.maxDragScrollIncrement '" +
                             this.maxDragScrollIncrement + "' to a valid value. This should be an " +
                             "absolute pixel value or a percentage to scroll by.");

            // cache for repeated scroll events
            this._maxHInc = parseInt(maxInc / 100 * this.getScrollWidth());
            this._maxVInc = parseInt(maxInc / 100 * this.getScrollHeight());
        } else {
            this._maxHInc = this._maxVInc = this.maxDragScrollIncrement;
        }

        if (!isc.isA.Number(this.minDragScrollIncrement)) {
            // resolve percentages
            var minInc = parseInt(this.minDragScrollIncrement);
            if (!isc.isA.Number(minInc))
                this.logWarn("Unable to resolve this.minDragScrollIncrement '" +
                             this.minDragScrollIncrement + "' to a valid value. This should be an " +
                             "absolute pixel value or a percentage to scroll by.");

            // cache for repeated scroll events
            this._minHInc = parseInt(minInc / 100 * (this.getScrollWidth()-viewportWidth));
            this._minVInc = parseInt(minInc / 100 * (this.getScrollHeight()-viewportHeight));
        } else {
            this._minHInc = this._minVInc = this.minDragScrollIncrement;
        }

        // Direction param - certain widgets only cause drag-scrolling either vertically
        // or horizontally. Derived by EH from canvas.dragScrollDirection, and passed into
        // this method as the direction param.
        // If we are passed a 'direction' parameter, only allow drag-scrolling in the
        // direction specified.
        var hDSDir = (direction == isc.Canvas.VERTICAL ? 0 : this._getHDragScrollDirection(offsetX)),
            vDSDir = (direction == isc.Canvas.HORIZONTAL ? 0 : this._getVDragScrollDirection(offsetY));

        // This event is fired on a timer.
        // We want to avoid scrolling in the case where a user passes over the scroll threshold
        // on one axis (which kicks off the timer), and is positioned over a different scroll
        // threshold when the timer executes, as we want to scroll only if the user has
        // consciously hovered over a scroll threshold.
        // Once we've kicked off the first scroll, we don't need to be strict about this -
        // if the user moves their mouse to a different scroll area while scrolling is in
        // progress we can assume it's a conscious attempt to scroll in another direction.
        if (firstScroll) {
            // if the direction has changed, set to zero - this will prevent scrolling from
            // occurring
            if (horizontal != 0 && horizontal != hDSDir)
                horizontal = 0;
            if (vertical != 0 && vertical != vDSDir)
                vertical = 0;
        } else {
            horizontal = hDSDir;
            vertical = vDSDir;
        }

        // When drag selecting text the correct behavior is a little tricky.
        // If a user is selecting a line of text which happens to be near the edge of
        // the viewport we don't want a scroll to occur on a parent and shoot the target
        // out of view.
        // Therefore we only drag scroll a parent as far as necessary to completely 
        // reveal the drag-target
        if (isDragSelect) {
            var dragTarget = isc.EH.dragTarget;
            if (this != dragTarget) {
                if (horizontal != 0) {
                    var offsetLeft = dragTarget.getCanvasLeft(this);
                    if (horizontal < 0) {
                        if (offsetLeft >= 0) {
                            horizontal = 0;
                        }
                    } else {
                        if (offsetLeft + dragTarget.getVisibleWidth() 
                            <= this.getViewportWidth()) 
                        {
                            horizontal = 0;
                        }
                    }
                }
                if (vertical != 0) {
                    var offsetTop = dragTarget.getCanvasTop(this);
                    if (vertical < 0) {
                        if (offsetTop >= 0) {
                            vertical = 0;
                        }
                    } else {
                        if (offsetTop + dragTarget.getVisibleHeight() 
                            <= this.getViewportHeight()) 
                        {
                            vertical = 0;
                        }
                    }
                }
            }
        }
        if (containsEvent) {
            hScrollIncrement = this.getScrollIncrement(horizontal,
                                                            offsetX,
                                                            viewportWidth,
                                                            this.getHDragScrollThreshold(),
                                                            this._maxHInc,
                                                            this._minHInc);
            vScrollIncrement = this.getScrollIncrement(vertical,
                                                            offsetY,
                                                            viewportHeight,
                                                            this.getVDragScrollThreshold(),
                                                            this._maxVInc,
                                                            this._minVInc);
        } else {
            hScrollIncrement = horizontal * this._maxHInc;
            vScrollIncrement = vertical * this._maxVInc;
        }

        // Don't bother scrolling / setting up repeating scrolls if we're already at the end
        if ((hScrollIncrement > 0 && (this.getScrollLeft() >= this.getScrollRight())) ||
            (hScrollIncrement < 0 && (this.getScrollLeft() <= 0))) hScrollIncrement = 0;
        if ((vScrollIncrement > 0 && (this.getScrollTop() >= this.getScrollBottom())) ||
            (vScrollIncrement < 0 && (this.getScrollTop() <= 0))) vScrollIncrement = 0;
    }

    if (hScrollIncrement != 0 || vScrollIncrement != 0) {
        this.scrollBy(hScrollIncrement, vScrollIncrement);
        // continue to scroll.  We do this on a timeout, rather than re-calling this method
        // directly to allow normal event processing to continue.
        this._dragScrollTimer = isc.Timer.setTimeout(
                                    {target:this,
                                     methodName:"_performDragScroll",
                                     args:[null,null,null,direction,isDragSelect]}, 50
                                );

    // The mouse has moved out of the scrollable area since we last started the timer, or
    // we've reached the edge of the widget.
    } else {
        // clear out the cached scroll increments - we'll lazily recalculate when drag
        // scrolling begins again.
        delete this._maxHInc;
        delete this._minHInc;
        delete this._maxVInc;
        delete this._minVInc;
    }
},

// Internal method to determine how much to scroll by when drag-scrolling this widget, based
// on mouse position [abstracted out to work vertically or horizontally]
// For drag scrolling to occur, the mouse must be positioned <= 1 * this.dragScrollThreshold
// from the edge of the viewport.
// The closer the mouse is to the edge of the viewport, the faster the widget will scroll
// - this is controlled by the 'maxDragScrollIncrement' / 'minDragScrollIncrement' properties
//   when the user is hovering right over the edge of the window, the window will scroll by the
//   maximum value, when hovering exactly 1* the threshold from the edge, it will scroll by the
//   minimum value.
// Return zero if the widget should not scroll.
getScrollIncrement : function (direction, eventOffset, viewportSize, threshold, maxInc, minInc) {
    if (direction == null || direction == 0) return 0;

    // Resolve the offset to the distance from the start of the scroll threshold
    if (direction > 0) {
        eventOffset = eventOffset - (viewportSize - threshold);
    } else if (direction < 0) {
        eventOffset = threshold - eventOffset;
    }

    // Don't scroll if we're outside the threshold area (or outside the widget)
    if (eventOffset < 0 || eventOffset > threshold) return 0;

    // Determine the amount to scroll based on the max/min scroll increments, and how close
    // we are to the edge of the widget.
    var increment = direction *
            (
                (eventOffset / threshold) *  (maxInc - minInc)
                + minInc
            );
    return parseInt(increment);
},
//<DragScrolling

// Overflow handling
// --------------------------------------------------------------------------------------------
// Managing what happens when contents overflow the Canvas' specified size: expanding, clipping,
// scrolling, etc


hasInherentHeight : function () {
    if (this.inherentHeight != null) return this.inherentHeight;
    return (this.children == null &&
            (this.overflow == isc.Canvas.VISIBLE || this.overflow == isc.Canvas.CLIP_H));
},

hasInherentWidth : function () {
    if (this.inherentWidth != null) return this.inherentWidth;
    return (this.children == null &&
            (this.overflow == isc.Canvas.VISIBLE || this.overflow == isc.Canvas.CLIP_V));
},

canOverflowWidth : function () {
    return this.overflow == isc.Canvas.VISIBLE || this.overflow == isc.Canvas.CLIP_H;
},

canOverflowHeight : function () {
    return this.overflow == isc.Canvas.VISIBLE || this.overflow == isc.Canvas.CLIP_V;
},

_shouldWriteClipDiv : function () {
    return (this.useClipDiv &&
            (!isc.Browser._useNewSingleDivSizing ||
             
             (this.overflow == isc.Canvas.VISIBLE && (!isc.Browser.isOpera || isc.Browser.minorVersion >= 11.1)))) ||
           this.getScrollingMechanism() == isc.Canvas.NESTED_DIV;
},

//>	@method	canvas.getOverflow()
//		Return the overflow of a Canvas.
//		@group	positioning, sizing
//
//<
getOverflow : function () {
    return this.overflow;
},


//>	@method	canvas.setOverflow()    ([A])
// Update the +link{Canvas.overflow, overflow} of a Canvas after it has been created.
//		@group	positioning, sizing
//		@param	newOverflow	(Overflow)		New overflow value.
//
// @group sizing
// @visibility external
//<
setOverflow : function (newOverflow) {

    //>Animation
    // Finish any hide/show animations that are running
    // Required since we force overflow to hidden during animation, then reset to original
    // overflow when animation completes.
    if (this._animatingHide != null && !this._hidingAsAnimation)
        this.finishAnimation(this._animatingHide);
    if (this._animatingShow != null && !this._showingAsAnimation)
        this.finishAnimation(this._animatingShow);
    //<Animation

    if (this.overflow == newOverflow) return; // no-op

    var oldOverflow = this.overflow;
	this.overflow = newOverflow;

    // If we're drawn, we need to update the elements width, height, overflow, and
    // clip so that they reflect the new overflow state.  Essentially, we make the setting
    // match the initial values as set in getTagStart() and then call 'adjustOverflow' to do
    // standard adjustments.

    if (!this.isDrawn()) return;

    var scrollingStateChanged = false;

    // If we WERE showing custom scrollbars, and the overflow is no longer scroll or auto,
    // clear them out
    if (newOverflow != isc.Canvas.SCROLL && newOverflow != isc.Canvas.AUTO &&
         (this.hscrollOn || this.vscrollOn))
    {
        if (this.hscrollOn || this.vscrollOn) scrollingStateChanged = true;
        // clear flags (note flag may be set with no scrollbar having been created)
        this.hscrollOn = this.vscrollOn = false;
        // hide custom scrollbars if they exist
        // (Note: we could destroy the scrollbars here, but it's not necessary - this is
        // quicker, and this way the scrollbars may be re-used if the overflow is changed
        // back to auto / scroll.  If the widget is clear()'d / destroy()'d, the scrollbars
        // will get cleaned up.)
        if (this.hscrollbar != null) this.hscrollbar.hide();
        if (this.vscrollbar != null) this.vscrollbar.hide();
    }

    
    if (isc.Browser.isIE &&
        (newOverflow == isc.Canvas.CLIP_H || newOverflow == isc.Canvas.CLIP_V))
    {
        this.markForRedraw();
        return;
    }

    var handle;

    // Check if we need to switch DOM structures from doubleDiv to singleDiv or vice versa.
    var drewClipDiv = this._drewClipDiv,
        writeClipDiv = this._shouldWriteClipDiv();
    if (isc.Browser._useNewSingleDivSizing && drewClipDiv != writeClipDiv) {
        handle = this.getHandle();
        var clipHandle = this.getClipHandle(),
            origParent = clipHandle.parentNode,
            origNextSibling = clipHandle.nextSibling,
            docFragment,
            child;

        
        if (isc.Browser.useCreateContextualFragment) {
            // Parse `this.getTagStart() + this.getTagEnd()' as a `DocumentFragment' and move
            // the child elements of the current handle into the document fragment.
            var range = handle.ownerDocument.createRange();
            range.selectNode(handle);
            docFragment = range.createContextualFragment(this.getTagStart() + this.getTagEnd());

            // Detach the current handle (and clip handle if applicable) from the document
            // so that moving the children of the current handle into `docFragment' will be more efficient.
            
            this._drewClipDiv = drewClipDiv;
            this.clearHandle(true);

            var newHandle = docFragment.firstChild;
            if (writeClipDiv) newHandle = newHandle.firstChild;

            while ((child = handle.firstChild) != null) {
                newHandle.appendChild(child);
            }

            // Attach the new handles and original handle's children (atomic operation).
            origParent.insertBefore(docFragment, origNextSibling);

            this._handle = newHandle;
        } else {
            // Use only DOM Level 1 methods for browsers that do not support Range.createContextualFragment().

            // Save the children of the handle to a `DocumentFragment'. First detach the current handle
            // (and clip handle if applicable) from the document so that this is more efficient.
            
            this._drewClipDiv = drewClipDiv;
            this.clearHandle(true);

            docFragment = handle.ownerDocument.createDocumentFragment();
            while ((child = handle.firstChild) != null) {
                docFragment.appendChild(child);
            }

            // Re-create the handle elements.
            var drawContext;
            if (origNextSibling == null) {
                drawContext = {
                    element: origParent,
                    position: "beforeEnd"
                };
            } else {
                drawContext = { element: origNextSibling };
            }
            this._insertHTML(false, drawContext);

            // Attach the original handle's children (atomic operation).
            this.getHandle().appendChild(docFragment);

            // A side effect of Canvas._insertHTML() is that it resets the _drawn flag. Set back
            // to true.
            this._drawn = true;
        }

        this._drewClipDiv = writeClipDiv;

        delete this._setToDisplayNone;
        delete this._visibleDisplayStyle;
        this._updateHandleDisplay();
    }

    handle = this.getStyleHandle();

    // set the overflow property on the handle
	handle.overflow = this._getHandleOverflow();
    // Set the initial width / height of the handle
    var sizeArray = this._getInitialHandleSize();
    handle.width = sizeArray[0] + this._$px;
    handle.height = sizeArray[1] + this._$px;

    

    // If we're not using clip-scrolling, we want to clear out any clip that's already been
    // set on the widget's handle.
    // (_adjustOverflow() will apply a clip to the handle in some cases, but will not remove
    // an existing clip)
    
    if (handle.clip != null && handle.clip != "" &&
        handle.clip != "rect(auto auto auto auto)")
    {
        handle.clip = (isc.Browser.isIE ? "rect(auto)" : "");
    }

    // The handle will now have it's overflow, size and clip set to the values they would
    // have at the end of getTagStart().
    // Call adjustOverflow to handle resizing to accommodate contents, setting additional
    // clips, showing scrollbars, etc.
	this.adjustOverflow("setOverflow");

    
    if (oldOverflow == isc.Canvas.VISIBLE && newOverflow != isc.Canvas.VISIBLE) {
        var dX = Math.max(this.getScrollWidth() - this.getInnerWidth(), 0),
            dY = Math.max(this.getScrollHeight() - this.getInnerHeight(), 0);
        // Note the handle in this case is shrinking by the difference between scroll size
        // and available space, so pass in -dX/-dY
        if (dX > 0 || dY > 0) this._resized(-dX, -dY, "overflow changed");

    } else if (oldOverflow != isc.Canvas.VISIBLE && newOverflow == isc.Canvas.VISIBLE) {
        var dX = Math.max(this.getScrollWidth() - this.getInnerWidth(), 0),
            dY = Math.max(this.getScrollHeight() - this.getInnerHeight(), 0);
        if (dX > 0 || dY > 0) this._resized(dX, dY, "overflow changed");
    } else if (scrollingStateChanged) {
        this.innerSizeChanged("scrolling state changed");
    }

    // Note: since the dynamic _canFocus() method defaults to depending on this.overflow
    // being hidden, we should call the _updateCanFocus() method here to update the native
    // handle focus and tab-index behavior.
    // NOTE: avoid doing this in common cases where it's not needed, since it can cause some
    // components to redraw (eg Buttons and Labels, when animating)
    if ((newOverflow == isc.Canvas.HIDDEN || newOverflow == isc.Canvas.VISIBLE) &&
        (oldOverflow == isc.Canvas.HIDDEN || oldOverflow == isc.Canvas.VISIBLE)) {
    } else {
        this._updateCanFocus();
    }
},

// set a timer to call adjustOverflow (unless one is already set).  This is to avoid redundant
// timers being set when parents set a timer to adjustOverflow on child move and resize, which
// can happen in large batches (particularly with layouts).  It might even be worthwhile to
// centralize delayed adjustOverflow() calls for all widgets into a single queue, similar to
// the redraw queue.
_markForAdjustOverflow : function (reason) {
    if (!this.isDrawn() || this.isDirty() || this.destroying || this._clearing) return;

    if (!this._overflowQueued) {
        //>DEBUG
        if (this.logIsDebugEnabled())
            this.logDebug("delaying adjustOverflow: " + (reason ? reason : this.getStackTrace()));
        //<DEBUG
        var theCanvas = this;
        this._overflowTimer =
            isc.Timer.setTimeout(function () {
                if (!theCanvas.destroyed) theCanvas.adjustOverflow(reason, true)
            }, 0);
    }
    this._overflowQueued = true;
},

//> @method canvas.adjustForContent() [A]
// This method tells a component to adjust for size changes made to content by external code.
// <P>
// This is for very advanced use in which the contents of a Canvas are being directly updated by
// Ajax techniques, which is required for integration with some third-party libraries.
// Calling this method is required because browsers do not provide consistent events by which
// SmartClient can be notified of external content changes.
// <P>
// Only contents supplied to a widget via +link{canvas.contents} or via an override of
// +link{canvas.getInnerHTML()} should be manipulated directly.  Contents automatically
// generated by SmartClient components (such as the basic structure of a Button) should never be
// manipulated: these structures are considered internal, differ by platform, and will change
// without notice.
// @param immediate (boolean)
//  By default the adjustment will occur on a small delay for performance reasons.
//  Pass in this parameter to force immediate adjustment.
// @group sizing
// @visibility external
//<
adjustForContent : function (immediate) {
    var reason = "adjustForContent() called";
    if (immediate) this.adjustOverflow(reason);
    else this._markForAdjustOverflow(reason);
},


_browserDoneDrawing : function () {
    var handle = this.getHandle();

    
    if (isc.Browser.isOpera) {
        var handle = this.getHandle();
        return !(handle.scrollHeight == 0 && handle.scrollWidth == 0);
    }

    if (!isc.Browser.isIE) {
        
        //
        // clipHandle can be null in canvas.start/end mode if we're doc.write()ing
        var clipHandle = this.getClipHandle();
        if (clipHandle == null) return false;

        var scrollHeight = clipHandle.scrollHeight;
        if (scrollHeight == null || scrollHeight == 0) scrollHeight = this.getClipHandle().offsetHeight;

        return scrollHeight != 0;
    }

    // IE-only (including MacIE)

    
    var browserDoneDrawing;
    if (isc.Browser.isWin) {
        
        
        return handle != null && handle.scrollHeight != this._$undefined &&
                    handle.scrollHeight != 0;
    }
},

// Flag to suppress adjustOverflow from running while we're waiting on a redraw
adjustOverflowWhileDirty:true,

//>	@method	canvas.adjustOverflow()	(A)
// Adjust the size, clipping and/or scrolling of a canvas to account for its drawn size and
// the overflow setting.
//
//		@group	sizing
//
//		@return	(boolean)	true == we're done adjusting the overflow
//							false == couldn't adjust for some reason, had to defer and call again.
//<
// NOTE: children must adjust for overflow before parents, because some parents take children's
// sizes into account when sizing themselves.
adjustOverflow : function (reason, delayed, fromRedraw) {
    if (isc._traceMarkers) arguments.__this = this;

    if (delayed && !this._overflowQueued) {
        // only one timer can be outstanding at once, but this case still happens if
        // adjustOverflow is called by other code during the timer delay (eg redraw)
        //this.logWarn("aborting unnecessary delayed adjust");
        return;
    }
    this._overflowQueued = false;


    // if not drawn yet or we're not handling overflow for this object,
    //	just return true since we don't need to do anything
    if (!this.isDrawn() || this.overflow == isc.Canvas.IGNORE) return true;

    // Flag can be set to avoid all adjustOverflows while waiting on a redraw
    // Note: if we're being redrawn in response to the redraw of a dirty parent, their
    // __dirty flag will not yet have been cleared (so this.isDirty() will return true).
    // We catch that case with the explicit 3rd param
    
    if (!this.adjustOverflowWhileDirty && !fromRedraw && this.isDirty() &&
        (this.overflow != isc.Canvas.VISIBLE))
    {
        return;
    }

    
    if (!isc.Page.isLoaded() &&
        (isc.Browser.isSafari ||
         (isc.Browser.isMoz && isc.Browser.geckoVersion < 20040616)))
    {
        // defer the 'adjustOverflow' until the page has loaded.
        isc.Page.setEvent("load", this, isc.Page.FIRE_ONCE, "_adjustOverflowForPageLoad");

        
        if (isc.Browser.isMoz) return;
    }

    // If we've set the internal flag to suppress adjustOverflow just return
    if (this._suppressAdjustOverflow) return;

    // adjust now if size is available
    if (this._browserDoneDrawing()) return this._adjustOverflow(reason);

    if (this.logIsDebugEnabled("overflow")) {
        this.logDebug("browser not done drawing, deferring overflow.", "overflow");
        if (this._drewClipDiv) {
            this.logDebug("clipHandle sizes: " + this.echoElementSize(this.getClipHandle()),
                          "overflow");
        }
        this.logDebug("handle sizes: " + this.echoElementSize(this.getHandle()),
                      "overflow");
    }

    // if size can't be determined, set a relatively immediate timer to check again.
    // If it still can't be determined, keep checking at a less frequent interval.
    if (!this._delayedAdjustOverflow) {
        this._markForAdjustOverflow();
        this._delayedAdjustOverflow = true;     // this flag cleared out in _adjustOverflow
    } else {
        //>DEBUG
        this.logDebug("still waiting for size to become available", "overflow"); //<DEBUG
        this._queueForDelayedAdjustOverflow();
    }
    // return false that we didn't finish the adjustment yet
    return false;
},


_adjustOverflowForPageLoad : function () {
    if (!this.destroyed && this.isDrawn()) this.adjustOverflow("pageLoad");
},

_queueForDelayedAdjustOverflow : function() {
    isc.Canvas._queueForDelayedAdjustOverflow(this.getID());
},


// (internal) routine to implement the Canvas.overflow property, by clipping, scrolling or
// automatically expanding based on the size of content and children
_adjustOverflow : function (reason) {
    if (this._inAdjustOverflow) {
        
        return
    }
    this._inAdjustOverflow = true;
    this.__adjustOverflow(reason);
    this._inAdjustOverflow = false;
},

_supportedOverflows:{hidden:true, visible:true, scroll:true, auto:true, "clip-v":true, "clip-h":true, ignore:true},
_$sizing : "sizing",
_$overflow : "overflow",
__adjustOverflow : function (reason) {
    if (!this._supportedOverflows[this.overflow]) {
        this.logWarn("This widget has overflow specified as " + this.echo(this.overflow) +
                     ".  This overflow setting is not supported - defaulting to overflow:\"visible\".");
        this.overflow = isc.Canvas.VISIBLE;
    }

    // Note: scrollHeight / scrollWidth and cacheing:
    // in getScrollHeight() / getScrollWidth(), we iterate through all our DOM children to
    // calculate a reliable scrollHeight / width.
    // This is quite expensive, so we cache the value after calculating it.
    //
    // This method (_adjustOverflow) is called by all the methods that could end up effecting
    // the scrollHeight / width of a widget (setContents, resize, addChildren...)
    // Therefore in this case we want ensure we calculate new scrollHeight / scrollWidth values.
    //
    // getScrollHeight() / getScrollWidth() takes a parameter 'calculateNewValue' which, if true,
    // will force the value to be recalculated rather than returning the cached value.
    // We must ensure that at the end of this method that getScrollHeight() and
    // getScrollWidth() will report accurate values.
    //
    // Start by invalidating any existing cached scrollHeight / scrollWidth values for this
    // widget.
    if (this._scrollWidth != null) delete this._scrollWidth;
    if (this._scrollHeight != null) delete this._scrollHeight;

    // if we allow content to overflow, this method may change our drawn size.
    // We need to detect this case and fire 'resized'.
    // old scroll size was remembered last time this method was run (if overflow == "visible")
    // - hang onto this value locally for comparison with the current drawn size.
    
    var oldScrollWidth = this._currentContentWidth,
        oldScrollHeight = this._currentContentHeight;

    delete this._currentContentWidth;
    delete this._currentContentHeight;

    // hang onto a flag indicating whether we're overflowed
    var wasOverflowed = this._isOverflowed;
    this._isOverflowed = false;

	// make a local reference to the global Canvas object (faster)
	var canvas = isc.Canvas;

    // clear out the _delayedAdjustOverflow flag used by the delayedAdjustOverflow queueing code
    this._delayedAdjustOverflow = null;

    //>DEBUG
    if (this.getHandle() == null) this.logWarn("adjustOverflow: handle null");
    if (this.getClipHandle() == null) this.logWarn("adjustOverflow: clipHandle null");

    if (this.alwaysShowVScrollbar) {
        // this is acceptable since overflow may be modified at runtime
        if (this.overflow != isc.Canvas.AUTO || this.overflow != isc.Canvas.SCROLL) {
            this.logInfo("alwaysShowVScrollbar specified as true, but overflow set to \""+
                         this.overflow + "\". Property will be ignored.");
        } else if (this.showCustomScrollbars == false) {
            this.logWarn("alwaysShowVScrollbar property not supported when showing native scrollbars");
        }
    }

    if (this.logIsInfoEnabled(this._$sizing)) {
        this.logInfo("Specified size: " + this.getWidth() + "x" + this.getHeight() +
                     ", drawn scroll size: " +
                            this.getScrollWidth(true) + "x" + this.getScrollHeight(true) +
                     ", border: " + this.getVBorderSize() + "x" + this.getHBorderSize() +
                     ", margin: " + this.getVMarginSize() + "x" + this.getHMarginSize() +
                     (oldScrollWidth == null ? "" :
                      ", old size: " + oldScrollWidth + "x" + oldScrollHeight) +
                     ", reason: " + reason,
                     "sizing");
    }

    if (this.logIsDebugEnabled(this._$sizing)) {
        if (this._drewClipDiv) {
            this.logDebug("clipHandle sizes: " + this.echoElementSize(this.getClipHandle()),
                          "sizing");
        }
        this.logDebug("handle sizes: " + this.echoElementSize(this.getHandle()),
                      "sizing");
    }
    //<DEBUG
    

	if (this.overflow == canvas.IGNORE) {
        
	} else if (this.overflow == canvas.VISIBLE) {

        // If we drew larger than the specified size, expand to that size.
        // Shrink if we were previously drawn larger than specified size, but never shrink
        // below specified size.

        
        var resetHandleOnAdjustOverflow = false;
        if (this._resetHandleOnAdjustOverflow) {
            if (this.getWidth() < this.getVisibleWidth() ||
                this.getHeight() < this.getVisibleHeight())
            {
                resetHandleOnAdjustOverflow = true;
                if (this.parentElement != null) {
                    this.parentElement.childResettingHandleForAdjustOverflow();
                    
                    this._setHandleRect(null, null, this.width, this._height);
                }
            }
            delete this._resetHandleOnAdjustOverflow;
        }

        var scrollWidth = this.getScrollWidth(true),
            scrollHeight = this.getScrollHeight(true);

        
        if (this._useMozScrollbarsNone) {
            var handle = this.getScrollHandle();
            if (handle.scrollTop != 0 || handle.scrollLeft != 0) {
                handle.scrollTop = handle.scrollLeft = 0;
                
            }
        }

        // If the widget's content or children take up more space than the specified size, the
        // drawn size may exceed the specified size.

        

        var innerWidth = this.getInnerWidth(), innerHeight = this.getInnerHeight();

        // figure out whether we're overflowed, and store it
        var overflowed = this._isOverflowed = (scrollWidth > innerWidth ||
                                               scrollHeight > innerHeight);

        // if we're not overflowed, and we weren't overflowed before, we don't need to resize
        // the handle.
        if (!overflowed && !wasOverflowed)
        {
            this._currentContentWidth = scrollWidth;
            this._currentContentHeight = scrollHeight;
            //this.logWarn("adjustOverflow done, no overflow, size: " +
            //             [scrollWidth, scrollHeight]);
            return;
        }

        //this.logWarn("proceeding to resize handle");

        // Resize to the larger of this.size and the reported scroll size [+ border and margin]
        // in each dimension.
        var hMarginBorder = this.getHMarginBorder(), vMarginBorder = this.getVMarginBorder();

        //this.logWarn("assigning width/height: " + [
        //                    Math.max((scrollWidth + hMarginBorder), this.getWidth()),
        //                    Math.max((scrollHeight + vMarginBorder), this.getHeight()) ] +
        //" margin/border is: " + [hMarginBorder,vMarginBorder]);
        var newWidth  = Math.max((scrollWidth  + hMarginBorder), this.getWidth()),
            newHeight = Math.max((scrollHeight + vMarginBorder), this.getHeight());

        

        
        if (isc.Menu && isc.isA.Menu(this.parentElement) && isc.isA.GridBody(this) &&
            (newWidth > this.getWidth() || newHeight > this.getHeight())) {
            this._resetHandleOnAdjustOverflow = true;
        }

        this._setHandleRect(this.left, this.top, newWidth, newHeight);

        
        if (this.isRTL()) this.handleMoved();

        
        var hasChildren = this.children && this.children.length > 0;
        if (!hasChildren || this.allowContentAndChildren) {
            var newScrollHeight = this.getScrollHeight(true),
                newScrollWidth = this.getScrollWidth(true);

            if (newScrollHeight != scrollHeight || newScrollWidth != scrollWidth) {
                

                scrollWidth = newScrollWidth;
                scrollHeight = newScrollHeight;

                this._setHandleRect(this.left, this.top,
                                Math.max((scrollWidth + hMarginBorder), this.getWidth()),
                                Math.max((scrollHeight + vMarginBorder), this.getHeight()));

                if (this.isRTL()) this.handleMoved();
            }
        }
        if (resetHandleOnAdjustOverflow && this.parentElement != null) {
            this.parentElement.childResetHandleForAdjustOverflowComplete();
        }


        
        if (this.snapTo != null && overflowed &&
            (reason == this._$parentDrawn || reason == this._$draw))
        {
            this._resolvePercentageSize(true);
        }

        // Remember the current scrollWidth / scrollHeight so we can tell if future
        // adjustOverflows change the drawn size.
        this._currentContentWidth = scrollWidth;
        this._currentContentHeight = scrollHeight;

        // if the scrollWidth or scrollHeight changed, for an overflow:visible widget this
        // indicates the visible height/width have changed, so fire resized().  Note that this
        // notification may fire for overflow being introduced, going away, or just changing.
        
        if ((oldScrollWidth != null && oldScrollWidth != scrollWidth) ||
            (oldScrollHeight != null && oldScrollHeight != scrollHeight))
        {
            // don't report overflow going away during a resize, it's redundant with the
            // resized() notification fired during resizeBy()
            
            if (!overflowed && reason == this._$resize) return;
            this._resized(scrollWidth - oldScrollWidth, scrollHeight - oldScrollHeight,
                         this._$overflow);
        }

	} else if (this.overflow == canvas.HIDDEN) {

		// set the width and height of the layer explicitly
		this._setHandleRect(this.left, this.top, this.getWidth(), this.getHeight());

        
        if (this.isRTL()) {
            var scrollingMechanism = this.getScrollingMechanism();
            if (scrollingMechanism == isc.Canvas.NATIVE) {
                this.scrollLeft = this.getScrollLeft();
            } else if (scrollingMechanism == isc.Canvas.NESTED_DIV) {
                this.scrollLeft = this.getScrollLeft();
            }
        }
        // If this adjustOverflow was fired because of a resize, our contents may no longer
        // overflow by the same amount, meaning we may be 'scrolled off the end'
        // call clampToContent() to fix this
        // Note: May not be required for all browsers - but some, including Moz, do allow
        // specifying a scroll height such that you're scrolled past all content in the handle
        this._clampToContent();


	} else if (this.overflow == canvas.CLIP_H) {
        // adjust the clip to the specified values horizontally, no matter how large it
        // rendered
        
        var scrollHeight = this.getScrollHeight(),
            vMarginBorder = this.getVMarginBorder(),
            drawnHeight = Math.max(scrollHeight + vMarginBorder, this.getHeight());

        this._currentContentHeight = drawnHeight;

        this.setClip(0,
                     this.getWidth(),
                     drawnHeight,
                     0);


		// set the width and height of the layer explicitly
        // (Setting the clip will not have changed the scrollHeight, so we don't need to pass
        // the calculateNewValues parameter in to force a new calculation).
		this._setHandleRect(this.left,
							this.top,
							this.getWidth(),
							drawnHeight);
	} else if (this.overflow == canvas.CLIP_V) {

        var scrollWidth = this.getScrollWidth(),
            hMarginBorder = this.getHMarginBorder();

        // handle scrollWidth not being reported as less than specified width
        if ((isc.Browser.isIE || isc.Browser.isMoz || isc.Browser.isOpera) &&
            (scrollWidth > this.getInnerWidth()) &&
            (this._currentContentWidth == scrollWidth)) {
            // Resize to specified size, then check scrollWidth again, and resize a second time
            // if necessary.
            this._setHandleRect(this.left, this.top,
                                this.getWidth(),
                                this.getHeight());

            // Recalculate the scrollWidth, and do a second resize, if it's greater
            // than the specified width now
            // (Pass in the 'calculateNewValues' parameter so it doesn't just return the
            // cached value).
            scrollWidth = this.getScrollWidth(true)

            if (scrollWidth > this.getInnerWidth()) {
                this._setHandleRect(this.left, this.top,
                                    scrollWidth + hMarginBorder,
                                    this.getHeight());
            }

        // Other browsers / double resize not required...
        // The reported scrollWidth should be accurate - just resize to fit content
        } else {

    		// set the width and height of the layer explicitly
    		this._setHandleRect(this.left,
                                this.top,
                                Math.max(scrollWidth + hMarginBorder, this.getWidth()),
                                this.getHeight());
        }

        var drawnWidth = Math.max(scrollWidth + hMarginBorder, this.getWidth());

		// adjust the clip to the specified values vertically, no matter how large it rendered
        this.setClip(0,
                     drawnWidth,
                     this.getHeight(),
                     0);

        // Remember the current scrollWidth
        this._currentContentWidth = drawnWidth;

	} else { // canvas.SCROLL, canvas.AUTO
        
        var scrollingMechanism = this.getScrollingMechanism();
        if ((isc.Browser.isIE && this.showCustomScrollbars && scrollingMechanism == isc.Canvas.NATIVE) ||
            scrollingMechanism == isc.Canvas.NESTED_DIV)
        {
            var scrollLeft = this.scrollLeft,
                scrollTop = this.scrollTop,
                trueScrollLeft = this.getScrollLeft(),
                trueScrollTop = this.getScrollTop();

            if (trueScrollLeft != scrollLeft || trueScrollTop != scrollTop) {
                this.scrollTo(trueScrollLeft, trueScrollTop, this._$nativeScroll);
            }
        }

        // old state of the scrollbars
        var vscrollWasOn = this.vscrollOn,
			hscrollWasOn = this.hscrollOn,
            wasFocusable = this._canFocus();

        // Permanently on v-scrollbar:
        var vScrollAlwaysOn = (this.alwaysShowVScrollbar && this.showCustomScrollbars);

		if (this.overflow == isc.Canvas.SCROLL) {
            // always show both scrollbars
			this.hscrollOn = this.vscrollOn = true;
		} else {	// Overflow is isc.Canvas.AUTO - determine whether scrollbars are required

            // scrollHeight / scrollWidth cache invalidated at the top of the method -no need for
            // 'calculateNewValues' parameter.

            var scrollHeight = this.getScrollHeight(),  height = this.getHeight(),
                scrollWidth = this.getScrollWidth(),    width = this.getWidth(),
                scrollbarSize = this.getScrollbarSize(),     scrollStateAtLayout;
            

            // If we're showing native scrollbars compare clientHeight / width with
            // specified height / width to determine whether we're showing scrollbars
            
            var vMarginBorder = this.getVMarginBorder(),
                hMarginBorder = this.getHMarginBorder();

            if (!this.showCustomScrollbars && this.getHandle().clientHeight != null) {
                this.hscrollOn = (this.getClipHandle().clientHeight < height - vMarginBorder);
                this.vscrollOn = vScrollAlwaysOn ||
                                 (this.getClipHandle().clientWidth < width - hMarginBorder);
            // Otherwise, we'll determine whether we need to show scrollbars in 2 steps:
            // If the content size exceeds the specified size, we definitely need
            // scrollbars.
            } else {
                this.vscrollOn = vScrollAlwaysOn ||
                                 ((scrollHeight - (height - vMarginBorder)) > 0);
                this.hscrollOn = (scrollWidth - (width - hMarginBorder)) > 0;
            }
            if ((this.vscrollOn && !vscrollWasOn && !this.hscrollOn) ||
                (this.hscrollOn && !hscrollWasOn && !this.vscrollOn))
            {
                
                if (this.showCustomScrollbars) {
                    
                    this._setHandleRect(this.left, this.top, this.getWidth(), this.getHeight());
                }

                // Call innerSizeChanged() to give the widget an opportunity to resize children to
                // match the new viewport.  Optimization: remember that we did this so we don't run
                // it redundantly if the scrolling state stays the same.
                scrollStateAtLayout = (this.vscrollOn ? "V" : "") + (this.hscrollOn ? "H" : "");
                this.innerSizeChanged("introducing scrolling");

                // now that the content has been reflown, get the new dimensions (pass in the
                // 'calculateNewValue' parameter - the value will have changed since the last
                // calculation)
                var newScrollWidth = this.getScrollWidth(true),
                    newScrollHeight = this.getScrollHeight(true);
                //>DEBUG
                if (this.logIsDebugEnabled("scrolling")) {
                    this.logDebug("Rechecking scrollWidth/Height on introduction of scroll:" +
                                  " old: " + [scrollWidth, scrollHeight] +
                                  ", new: " + [newScrollWidth, newScrollHeight],
                                  "scrolling");
                } //<DEBUG
                scrollWidth = newScrollWidth;
                scrollHeight = newScrollHeight;
            }
            
            if (this.vscrollOn && !this.hscrollOn) {
                if (this.showCustomScrollbars || (this.getClipHandle().clientHeight == null))
                    this.hscrollOn = scrollWidth - (width - hMarginBorder - scrollbarSize) > 0;
                else
                    this.hscrollOn =
                        (height > this.getClipHandle().clientHeight + this.getVBorderSize());

            } else if (this.hscrollOn) {

                if (this.showCustomScrollbars || (this.getClipHandle().clientWidth == null))
                    this.vscrollOn = vScrollAlwaysOn ||
                                    (scrollHeight - (height - vMarginBorder - scrollbarSize) > 0);
                else
                    this.vscrollOn = vScrollAlwaysOn ||
                        (width > this.getClipHandle().clientWidth + this.getHBorderSize());
            }
        }

        //>DEBUG Report scroll state
        if (this.logIsInfoEnabled("scrolling")) {
            this.logInfo("Drawn size: " + this.getScrollWidth(true) + " by " + this.getScrollHeight(true) +
                         ", specified: " + this.getWidth() + " by " + this.getHeight() +
                         ", scrollbar state: " + (this.hscrollOn ? "h" : "") +
                         (this.vscrollOn ? "v" : ""), "scrolling");
        } //<DEBUG

        
        if (this.showCustomScrollbars &&
            (this.hscrollOn != hscrollWasOn || this.vscrollOn != vscrollWasOn))
        {

            
            this._setHandleRect(this.left, this.top, this.getWidth(), this.getHeight());
            // Invalidate the cached scrollheight / width again..
            if (this._scrollWidth != null) delete this._scrollWidth;
            if (this._scrollHeight != null) delete this._scrollHeight;
        }

        var oldState = ((vscrollWasOn ? "V" : "") + (hscrollWasOn ? "H" : "")),
            newState = ((this.vscrollOn ? "V" : "") + (this.hscrollOn ? "H" : ""));
        if (oldState != newState) {
            //>DEBUG
            this.logInfo("Scrollbar state: " + oldState + " -> " + newState,
                         "scrolling"); //<DEBUG

            // call layout children since the viewport size changed
            // Optimization: if we ran innerSizeChanged() just above because a scrollbar was
            // newly introduced, and we're still in the same scrolling situation, no need to
            // run it again.
            if (scrollStateAtLayout == null || newState != scrollStateAtLayout)
            {
                this.innerSizeChanged("scrolling state changed");
            }
        }

        if (this.isRTL()) {
            
            if (this.hscrollOn && !hscrollWasOn) {
                var actualScroll = this.getScrollLeft();
                //this.logWarn("on RTL hscroll introduction, picked up scroll of: " + actualScroll +
                //             ", was: " + this.scrollLeft);
                this.scrollLeft = actualScroll;
            }

            // In RTL mode, when the vertical scrollbar is shown or hidden, any cached left
            // offset coordinates are invalidated.
            if (!!this.vscrollOn != !!vscrollWasOn) {
                this._$leftCoords = null;
            }
        }

        // if we're using native CSS scrollbars, we're done.  We just needed to figure out if
        // the browser was showing scrollbars.

		// if using custom scrollbars, show/hide scrollbars
		if (this.showCustomScrollbars) {
			// hide scrollbars if necessary here instead of later on, as calling scrollTo (see
			// below) will cause the other scrollbar to be redrawn if it isn't hidden yet.
			if (!this.hscrollOn && hscrollWasOn) this.hscrollbar.hide();
			if (!this.vscrollOn && vscrollWasOn) this.vscrollbar.hide();

			if (this.hscrollOn) {
				// if we need to scroll horizontally
				this._setHorizontalScrollbar();
			} else {
				// make sure we're not scrolled, scrollbar should already be hidden above.
				if (hscrollWasOn) this.scrollTo(0, null, "ending hscroll");
			}

			if (this.vscrollOn) {
				this._setVerticalScrollbar();
			} else {
				// make sure we're not scrolled, scrollbar should already be hidden above.
				if (vscrollWasOn) this.scrollTo(null, 0, "ending vscroll");
			}

            // Ensure we're not scrolled past our content
            
            this._clampToContent();
        }

        // default focusability is based on whether a widget scrolls, so if we have introduced
        // or removed scrolling, focusability *may* have changed.
        
        if ((this._useNativeTabIndex || this._useFocusProxy) &&
            wasFocusable != this._canFocus())
        {
            this._updateCanFocus();
        }

	}

    this._scrollRight = this.getScrollRight();

	return true;
},

// called during adjustOverflow.
// if our scrollHeight / scrollWidth has changed such that we're scrolled off the
// end, snap back to the end
_clampToContent : function () {
    // not scrolled, doesn't apply
    if (this.scrollLeft == 0 && this.scrollTop == 0) return;

    var maxScrollTop = Math.max(0, this.getScrollBottom()),
        maxScrollLeft = Math.max(0, this.getScrollRight()),
        newScrollLeft = this.getScrollLeft(),
        newScrollTop = this.getScrollTop(),
        clampToContent = false
    ;
    if (newScrollLeft > maxScrollLeft) {
        clampToContent = true;
        newScrollLeft = maxScrollLeft;
    }
    if (newScrollTop > maxScrollTop) {
        clampToContent = true;
        newScrollTop = maxScrollTop;
    }
    if (clampToContent) {
        this.scrollTo(newScrollLeft, newScrollTop, "clampToContent");
    }

},

// Verify that the (native) scroll position of the widget matches the recorded
// 'scrollLeft / scrollTop' properties.
// If the positions do not match, will scroll to the specified scroll position.

checkNativeScroll : function () {
    if (!this.isDrawn() || this.getScrollingMechanism() != isc.Canvas.NATIVE) return;

    var handle = this.getScrollHandle(),
        trueScrollLeft = handle.scrollLeft,
        trueScrollTop = handle.scrollTop;
    if (this.isRTL()) {
        trueScrollLeft = this._adjustScrollLeftForRTL(trueScrollLeft);
    }

    if (trueScrollLeft != this.scrollLeft || trueScrollTop != this.scrollTop) {
        //this.logWarn("noticed handle scrolled to: " +
        //             [trueScrollLeft, trueScrollTop]);
        
        this.scrollTo(this.scrollLeft, this.scrollTop, "removing native scroll");
        
    }
},

//>	@method	canvas._setHorizontalScrollbar()	(A)
//			Creates a horizontal custom scrollbar on a widget
//          returns true for success, false for failure
//		@group	scrolling
//
//<
_setHorizontalScrollbar : function () {

    // if the horizontal scrollbar hasn't been created, do so
    var scrollbar = this.hscrollbar;
    if (!scrollbar) {
        scrollbar = this.hscrollbar = isc.ClassFactory.newInstance(this.scrollbarConstructor,
		{
    		ID:this.getID()+"_hscroll",
    		autoDraw:false,
            _generated:true,
            zIndex:this.getZIndex() +1,
    		vertical:false,
    		scrollTarget:this,
            visibility:this.visibility,
    		_redrawWithMaster:false,
            _resizeWithMaster:false,
    		_redrawWithParent:false,
    		_selfManaged:false
        });
    }

    
	if (!isc.Page.isLoaded()) {
        var theCanvas = this;
		isc.Page.setEvent("load", function () {
            if (!theCanvas.destroyed) theCanvas._setHorizontalScrollbar()
        });
		return;
	}

    // the need for scrolling may go away while we are waiting to draw
    if (!this.hscrollOn) return;

    scrollbar.setRect(this.getOffsetLeft() + this.getLeftMargin() +
                        (this.vscrollOn && this.isRTL() ? this.getCustomScrollbarSize() : 0),
                      this.getOffsetTop() + this.getHeight() -
                              (this.getBottomMargin() + this.getCustomScrollbarSize()),
                      this.getOuterViewportWidth(),
                      this.getCustomScrollbarSize());

    if (!scrollbar.masterElement) {
        // if we haven't added it as a peer yet, add it (which will draw it)
        this.addPeer(scrollbar);
    } else {
		// otherwise show it
		if (this.visibility != isc.Canvas.HIDDEN) scrollbar.show();
	}
},


// when we're creating a custom scrollbar - this method returns a size for the sb.
// Usually governed by this.scrollbarSize - however if we're using the special "NativeScrollbar"
// class we need to ask that to give us the size the scrollbar will render at -- we can't
// control this.
getCustomScrollbarSize : function () {
    var scrollbarClass = this.scrollbarConstructor;
    if (isc.isA.String(scrollbarClass)) scrollbarClass = isc[scrollbarClass];

    if (isc.NativeScrollbar != null &&
        scrollbarClass == isc.NativeScrollbar) return isc.NativeScrollbar.getScrollbarSize();
    return this.scrollbarSize;
},

//>	@method	canvas._makeVerticalScrollbar()	(A)
//			Creates a vertical custom scrollbar on a widget
//          returns true for success, false for failure
//		@group	scrolling
//<
_setVerticalScrollbar : function () {
    var scrollbar = this.vscrollbar
    if (!scrollbar) {
	    // if the vertical scrollbar hasn't been created, do so
		scrollbar = this.vscrollbar = isc.ClassFactory.newInstance(this.scrollbarConstructor,
		{
		    ID:this.getID()+"_vscroll",
			autoDraw:false,
            _generated:true,
            zIndex:this.getZIndex() +1,
			vertical:true,
			scrollTarget:this,
            visibility:this.visibility,
			_redrawWithMaster:false,
            _resizeWithMaster:false,
			_redrawWithParent:false,
			_selfManaged:false
		});
    }

	// see _makeHorizontalScrollbar
	if (!isc.Page.isLoaded()) {
        var theCanvas = this;
		isc.Page.setEvent("load", function () {
            if (!theCanvas.destroyed) theCanvas._setVerticalScrollbar()
        });
		return;
	}

    if (!this.vscrollOn) return;

    // make sure we're showing the corner if we should be doing so
    // this will mark as dirty if necessary
    scrollbar.setShowCorner(this.hscrollOn && this.vscrollOn);

    scrollbar.setRect(
        this.getOffsetLeft() +
            (this.isRTL() ? this.getLeftMargin() :
                            this.getWidth() - (this.getRightMargin() + this.getScrollbarSize())),
        this.getOffsetTop() + this.getTopMargin(),
        this.getScrollbarSize(),
        this.getHeight() - this.getVMarginSize()
    );

    if (!scrollbar.masterElement) {
        // if we haven't added it as a peer yet, add it (which will draw it)
		this.addPeer(scrollbar);
    } else {
		// otherwise show it
		if (this.visibility != isc.Canvas.HIDDEN) scrollbar.show();
	}
},

// Scrollbar API
// -----------------------------------------------------------------------------------------
// Principally used by custom scrollbars

// scroll by slightly less than one viewport (less than in order to keep context)
scrollByPage : function (vertical, direction, reason) {
    var distance = (vertical ? this.getViewportHeight() : this.getViewportWidth()) -
             this.scrollDelta;
    this._scrollByAmount(vertical, direction * distance,
                         reason || "scrollByPage");
},

// scroll by one (arbitrary) increment
scrollByDelta : function (vertical, direction, reason) {
    this._scrollByAmount(vertical, direction * this.scrollDelta,
                         reason || "scrollByDelta");
},

_scrollByAmount : function (vertical, amount, reason) {
    if (vertical) {
        this.scrollTo(null, this.getScrollTop() + amount, reason);
    } else {
        this.scrollTo(this.getScrollLeft() + amount, null, reason);
    }
},

canScroll : function (vertical) {
    var scrollSize = vertical ? this.getScrollHeight() : this.getScrollWidth(),
        viewportSize = vertical ? this.getViewportHeight() : this.getViewportWidth();
    return (scrollSize > viewportSize);
},

// get the amount scrolled as a proportion of the maximum scroll amount, as a number between 0
// and 1
getScrollRatio : function (vertical) {
    var scrollSize = vertical ? this.getScrollHeight() : this.getScrollWidth(),
        viewportSize = vertical ? this.getViewportHeight() : this.getViewportWidth(),
        scrollPosition = vertical ? this.getScrollTop() : this.getScrollLeft(),
        // the furthest viewport position is when the viewport is showing the end of the
        // scrollable area
        maxScrollPosition = scrollSize - viewportSize;

    //this.logWarn("scrollSize: " + scrollSize +
    //             ", scrollPosition: " + scrollPosition);

    if (maxScrollPosition == 0) return 0;
    return scrollPosition / maxScrollPosition;
},

// scroll to some ratio of the maximum scroll amount
scrollToRatio : function (vertical, ratio, reason) {
    var maxScroll = Math.max(0, (vertical ? this.getScrollBottom() : this.getScrollRight())),
        newCoord = Math.round(maxScroll * ratio),
        reason = reason || "scrollToRatio";
    if (vertical) {
        this.scrollTo(null, newCoord, reason);
    } else {
        this.scrollTo(newCoord, null, reason);
    }
},

// get the ratio of the viewport size vs total content (used for thumb sizing)
getViewportRatio : function (vertical) {
    if (vertical) {
        return this.getViewportHeight() / this.getScrollHeight();
    } else {
        return this.getViewportWidth() / this.getScrollWidth();
    }
},

// Scrolling
// --------------------------------------------------------------------------------------------

//>	@method	canvas.getScrollBottom()
// Returns the scrollTop required to scroll vertically to the end of this widget's content.
// @return (int) scroll bottom coordinate
// @group scrolling
// @visibility external
//<
getScrollBottom : function () {
    if (this.overflow == isc.Canvas.VISIBLE) return 0;
    return this.getScrollHeight() - this.getViewportHeight();
},

//>	@method	canvas.getScrollRight()
// Returns the scrollLeft required to scroll horizontally to the end of this widget's content.
// @return (int) scroll bottom coordinate
// @group scrolling
// @visibility external
//<
getScrollRight : function () {
    if (this.overflow == isc.Canvas.VISIBLE) return 0;
    return this.getScrollWidth() - this.getViewportWidth();
},


//>	@method	canvas.scrollToTop()
// Vertically scrolls the content of the widget to 0
//
// @group scrolling
// @visibility external
//<
scrollToTop : function () {
    this.scrollTo(null, 0, "scrollToTop");
},
//>	@method	canvas.scrollToBottom()
// Vertically scrolls the content of the widget to the end of its content
//
// @group scrolling
// @visibility external
//<
scrollToBottom : function () {
    this.scrollTo(null, this.getScrollBottom(), "scrollToBottom")
},

//>	@method	canvas.scrollToLeft()
// Horizontally scrolls the content of the widget to 0
//
// @group scrolling
// @visibility external
//<
scrollToLeft : function () {
    this.scrollTo(0, null, "scrollToLeft");
},
//>	@method	canvas.scrollToRight()
// Horizontally scrolls the content of the widget to the end of its content
//
// @group scrolling
// @visibility external
//<
scrollToRight : function () {
    this.scrollTo(this.getScrollRight(), null, "scrollToRight");
},

//>	@method	canvas.scrollBy()
// Scroll this widget by some pixel increment in either (or both) direction(s).
//
// @param dX (number) Number of pixels to scroll horizontally
// @param dY (number) Number of pixels to scroll vertically
// @group scrolling
// @visibility external
//<
scrollBy : function (dX, dY, reason) {
    var left, top;
    if (dX != null) left = this.getScrollLeft() + dX;
    if (dY != null) top = this.getScrollTop() + dY;

    return this.scrollTo(left, top, reason || "scrollBy");
},

//>	@method	canvas.scrollByPercent()   ([])
//  Scroll this widget by some percentage of scroll size in either (or both) direction(s).
//
//      @visibility external
//      @param  dX  (number | string)    Percentage to scroll horizontally. Will accept either
//                                      a numeric percent value, or a string like "10%".
//      @param  dY  (number | string)    Percentage to scroll horizontally. Will accept either
//                                      a numeric percent value, or a string like "10%".
//		@group	scrolling
//<
scrollByPercent : function (dX, dY) {
    if (isc.isA.String(dX)) dX = parseInt(dX);
    if (isc.isA.String(dY)) dY = parseInt(dY);

    // Resolve bad coordinates or null values to zero
    if (!isc.isA.Number(dX)) dX  = 0;
    else
        // Note - "100%" scrolled is scrolled to the the scrollHeight - viewport height, as we're
        // moving the top / left edge of the viewport.
        dX = parseInt( dX / 100 * Math.max(0, (this.getScrollWidth()-this.getViewportWidth()) ) );
    if (!isc.isA.Number(dY)) dY  = 0;
    else
        dY = parseInt( dY / 100 * Math.max(0, (this.getScrollHeight()-this.getViewportHeight()) ) );

    this.scrollBy(dX, dY);
},

//>	@method	canvas.scrollTo()   ([])
// Scrolls the content of the widget so that the origin (top-left corner) of the content
// is left pixels to the left and top pixels above the widget's top-left corner (but still
// clipped by the widget's dimensions).
// <p>
// This is guaranteed to be called whenever this Canvas is scrolled, whether scrolling is
// initiated programmatically, by custom scrollbars, or a by a native scrollbar.
//
//      @visibility external
//		@group	scrolling
//		@param	[left]	(Integer)    the left coordinate
//		@param	[top]	(Integer)    the top coordinate
//<
//>Animation additional 'animating' parameter passed if this is part of an animated scroll
//<Animation
scrollTo : function (left, top, reason, animating) {
//!DONTOBFUSCATE this function is legal to observe and grab parameters
   if (isc._traceMarkers) arguments.__this = this;

    //>Animation
    if (!animating) {
        if (this.scrollAnimation) this.finishAnimation("scroll");
        // We slide in and out of view by adjusting scroll positions - if we're in the middle
        // of such an animation, just suppress future scrolls.
        
        if (this.hideAnimation && this.$hideAnimationInfo.slideOut)
            this.$hideAnimationInfo.slideOut = false;
        if (this.showAnimation && this.$showAnimationInfo.slideIn)
            this.$showAnimationInfo.slideIn = false;
    }
    //<Animation

    //>DEBUG
    if (this.logIsDebugEnabled("scrolling")) {
        this.logDebug("scrollTo(" + left + ", " + top +
                      "), reason: " + reason, "scrolling");
    } //<DEBUG

	if (!isc.isA.Number(left)) left = this.getScrollLeft();
	if (!isc.isA.Number(top)) top = this.getScrollTop();

    // if scrolling is actually occuring
    var actuallyMoved = false;
    if ((left != null && left != this.scrollLeft) || (top != null && top != this.scrollTop)) {
        actuallyMoved = true;
        // save off the last scroll coordinates, to allow detecting scrolling direction
        this.lastScrollLeft = this.scrollLeft;
        this.lastScrollTop = this.scrollTop;
        //this.logWarn("left, top: " + [left, top] +
        //             ", scrollLeft/Top: " + [this.scrollLeft, this.scrollTop]);
        this.lastScrollDirection = (left != null && left != this.scrollLeft &&
                                    top != null && top != this.scrollTop ? "both" :
                                    top != null && top != this.scrollTop ? "vertical" :
                                    "horizontal");
    }

    // if we're responding to a native scroll event, the viewport has already been scrolled by
    // the browser - we're just being notified, and we only call this method for the sake of
    // observers, and to update custom scrollbar thumbs. In this case, or if we haven't yet
    // been drawn, just store the passed in values.
    if (reason == "nativeScroll" || !this.isDrawn()) {
        this.scrollLeft = left;
        this.scrollTop = top;
    } else {
        // Don't scroll past the ends of the widget - this way the callers don't have to worry
        // about passing in good parameters.
        
        var maxScrollLeft = this.getScrollRight();
        this.scrollLeft = Math.max(0, Math.min(maxScrollLeft, left));
        var maxScrollTop = this.getScrollBottom();
        this.scrollTop = Math.max(0, Math.min(maxScrollTop, top));

        

        // Actually scroll the widget.
        this._scrollHandle(this.scrollLeft, this.scrollTop);
    }

    // update thumb position and size.  NOTE: because scrollbar construction is delayed under
    // some circumstances, we might not have a scrollbar yet even if scroll is on.
    if (this.showCustomScrollbars) {
        if (this.hscrollOn && this.hscrollbar) this.hscrollbar.setThumb();
        if (this.vscrollOn && this.vscrollbar) this.vscrollbar.setThumb();
    }

    // fire notification of scroll change
    if (actuallyMoved) this._scrolled(this.lastScrollLeft - this.scrollLeft,
                                      this.lastScrollTop  - this.scrollTop);
},


// canvas.scrolled()
//  Observable method called whenever a Canvas is explicitly moved.
//  Documented under registerStringMethods
scrolled : function (deltaX, deltaY) {},

_scrolled : function (deltaX, deltaY) {
    // If the mouse is over us and we scrolled (for example due to mouse wheel scroll / drag scroll)
    // fire a synthetic mousemove event on the event target
    // This means we can react to the fact that the mouse's position over our content has changed
    // (EG: update styling on list grid rows as the user scrolls with the mouse-wheel)
    
    
    if (!isc.EH._handlingMouseMove && !isc.Browser.nativeMouseMoveOnCanvasScroll) {
        this._fireSyntheticMouseMove();
    }
    this._fireParentScrolled(this, deltaX, deltaY);

    if (this.scrolled) this.scrolled(deltaX, deltaY);
},

// Helper method - if the mouse is over a widget and it scrolls, fire a synthetic
// mouseMove event to reflect the fact that the mouse-position has changed relative to
// the content of the widget
_fireSyntheticMouseMove : function () {

        // We only want to fire a mouse move if we are the current mouse target or a parent
        // of it.
        // This avoids cases where the mouse isn't over us, or some non child is occluding us
        // like an external drag-target.

        // Determine the target for the mouse move event based on event.target or
        // event.lastMoveTarget for non-mouse events.
        var lastEvent = isc.EH.lastEvent,
            isMouseEvent = isc.EH.isMouseEvent(lastEvent.eventType),
            currentlyOver =  isMouseEvent ? lastEvent.target : isc.EH.lastMoveTarget;

        if (currentlyOver != null) {
            if (!this.contains(currentlyOver, true)) currentlyOver = null;

            // If this was a mouse event, assume the reported target on the event is accurate
            //
            // Otherwise we're relying on the captured lastMoveTarget which was updated
            // last time mouseMove fired.
            // This may be out of date due to a scroll shifting the target out from under
            // the mouse -- will only happen if the lastMoveTarget is a child of the
            // widget that was scrolled (us).
            // In this case, check visibleAtPoint() to ensure the mouse is still over the
            // target. Pass this component in as the "upToParent" to make the method more
            // efficient. This asserts that the mouse is over our viewport somewhere - a
            // reasonable assumption since it was at the last mouseMove, and our scroll may
            // shift our childrens' page coords but won't change ours.
            
            
            else if (!isMouseEvent && currentlyOver != this) {
                var offsetX = this.getOffsetX(),
                    offsetY = this.getOffsetY();

                if (!currentlyOver.visibleAtPoint(isc.EH.getX(), isc.EH.getY(),
                    false, null, this))
                {
                    currentlyOver = null;
                }
            }

            
            if (currentlyOver != null) {
                this._firingSyntheticMouseMove = true;
                isc.EH._handleMouseMove(null, isc.EH.lastEvent);
                delete this._firingSyntheticMouseMove;

            }
        }
},
// canvas.parentMoved()
//  Observable method called whenever a Canvas's ancestor is explicitly moved.
//  Documented under registerStringMethods
parentScrolled : function (parent, deltaX, deltaY) {},

// If our parent has scrolled, inform any children we have that an ancestor has scrolled.
// This notifies the children that they will have been repositioned in terms of page
// coordinates.
handleParentScrolled : function (parent, deltaX, deltaY) {

    
    if (isc.Element.cacheOffsetCoords) this._$leftCoords = this._$topCoords = null;
	this.parentScrolled(parent, deltaX, deltaY);

	// fireParentScrolled is what notifies our children (recursively) that we scrolled.
	this._fireParentScrolled(parent, deltaX, deltaY);
},

// fire 'handleParentScrolled' on children. This will recursively call back into this
// method to notify all descendents.
_fireParentScrolled : function (parent, deltaX, deltaY) {
	var children = this.children;
	if (children != null) {
		for (var i = 0; i < children.length; i++) {
			// NOTE: this fires before during init, before children have necessarily been
			// auto-created
			if (isc.isA.Canvas(children[i])) {
				children[i].handleParentScrolled(parent, deltaX, deltaY);
			}
		}
    }
},


_childrenCoordsChanged : function () {
    if (!isc.Element.cacheOffsetCoords) return;

    var children = this.children;
    if (children != null && children.length > 0) {
        for (var i = 0; i < children.length; i++) {
            // clear offsetCoordinate cache
            children[i]._$leftCoords = children[i]._$topCoords = null;
            children[i]._childrenCoordsChanged();

        }
    }
},

//>	@method	canvas.scrollToPercent()   ([])
//  Scroll this widget to some position specified as a percentage of scroll size in either
// (or both) direction(s).
//
//      @visibility external
//      @param  left (number | string)    Left Percentage position to scroll to
//                                        Will accept either a numeric percent value, or a
//                                        string like "10%".
//      @param  top (number | string)    Top Percentage position to scroll to
//                                       Will accept either a numeric percent value, or a
//                                       string like "10%".
//		@group	scrolling
//<
scrollToPercent : function (left, top, reason) {
    if (isc.isA.String(left)) left = parseInt(left);
    if (isc.isA.String(top)) top = parseInt(top);

    // Resolve bad coordinates or null values to zero
    if (!isc.isA.Number(left)) left  = 0;
    if (!isc.isA.Number(top)) top  = 0;

    // Note - "100%" scrolled is scrolled to the the scrollHeight - viewport height, as we're
    // moving the top / left edge of the viewport.
    left = parseInt( left / 100 * Math.max(0, (this.getScrollWidth()-this.getViewportWidth()) ) );
    top = parseInt( top / 100 * Math.max(0, (this.getScrollHeight()-this.getViewportHeight()) ) );

    this.scrollTo(left, top, reason || "scrollToPercent");
},


_adjustScrollLeftForRTL : function (left, isSCCoords) {
    // Moz / Old IE -- scrolled hard left reported as negative values - we want to remap to
    // a zero-origin based system
    var adjustForNegativeOrigin =
            isc.Browser.isMoz || (isc.Browser.isIE && isc.Browser.minorVersion < 5.5),
        // IE9 with HTML5 doctype inverts the origin, so zero is hard right and the scrollLeft
        // reported value increases as the user scrolls leftwards.
        // Ditto with IE8 [tested with both HTML5 doctype and HTML4.01 strict doctype]
        // Backcompat mode (no doctype specified) does not inverse the origin.
        adjustForInverseOrigin = (isc.Browser.isIE8 || isc.Browser.isIE9)
                                 && isc.Browser.isStrict;

    if (!adjustForNegativeOrigin && !adjustForInverseOrigin) return left;

    var scrollWidth = this.getScrollWidth(),
        viewportWidth = this.getViewportWidth();
    return isc.Canvas._adjustScrollLeftForRTL(left, scrollWidth, viewportWidth, isSCCoords,
        adjustForNegativeOrigin, adjustForInverseOrigin);
},


_shiftScrollLeftOrigin : function (left, zeroToNegativeOrigin) {
    var scrollWidth = this.getScrollWidth(),
        viewportWidth = this.getViewportWidth();
    return isc.Canvas._adjustScrollLeftForRTL(left, scrollWidth, viewportWidth,
                        zeroToNegativeOrigin, true);
},

//>	@method	canvas._scrollHandle()   (IA)
// Internal method to scroll the widget's viewport to the left / top coordinates passed in.
// Called by canvas.scrollTo();
//
//      @visibility internal
//		@group	scrolling
//		@param	left	(number)    the left coordinate
//		@param	top	(number)    the top coordinate
//      @see    scrollTo()
//<
_scrollHandle : function (left, top) {
    var scrollMechanism = this.getScrollingMechanism();

    // for browsers that support setting scrollLeft/scrollTop to scroll.
    if (scrollMechanism == isc.Canvas.NATIVE) {

        if (this.isRTL()) {
            left = this._adjustScrollLeftForRTL(left, true);
        }

        var handle = this.getScrollHandle();
		if (handle) {
            // set a flag to tell '_handleCSSScroll' to No-Op while the scroll is in progress
            this._scrollingHandleDirectly = true;

			handle.scrollLeft = left;
			handle.scrollTop = top;

            

            
            var newScrollLeft = handle.scrollLeft,
                newScrollTop = handle.scrollTop;
            if (this.isRTL()) {
                newScrollLeft = this._adjustScrollLeftForRTL(newScrollLeft);
            }

            if (newScrollLeft != this.scrollLeft || newScrollTop != this.scrollTop) {

                //this.logWarn("handle clamping scrollLeft/Top at: " +
                //             [newScrollLeft, newScrollTop] +
                //             " tried to assign: " + [this.scrollLeft, this.scrollTop]);
                this.scrollLeft = newScrollLeft;
                this.scrollTop = newScrollTop;
            }

            
            delete this._scrollingHandleDirectly;

		}

    // scrolling mechanisms for browsers in which assigning to handle.scrollLeft/scrollTop
    // doesn't work.
    } else if (scrollMechanism == isc.Canvas.NESTED_DIV) {
        // move the contentDiv around within the clipDiv to create scrolling

        // Note that the contentDiv's parent is the clipDiv, and the contentDiv is always drawn
        // at (0,0) within the clipDiv, so we don't worry about left and top with respect to a
        // parent, since that applies only to the clipDiv and it's parent.

        // get the contentDiv
        var handle = this.getHandle();
        if (handle == null) {
            //>DEBUG this happens to ListGrid headers in NS6 when the LV scrolls it on
            // LV.draw().
            this.logWarn(this.getCallTrace(arguments) + " in NS6 with null handle");
            //<DEBUG
            return;
        }
        // Grab the style attribute for the handle
        handle = handle.style;

        if (this.isRTL()) {
            left = this._shiftScrollLeftOrigin(-left);
            top = -top;
        } else {
            left = -left;
            top = -top;
        }

        //this.logWarn("handle is at: " + [handle.left, handle.top] +
        //             ", moving to: " + [-this.scrollLeft, -this.scrollTop]);
        // move it within the clipDiv to create scrolling
        handle.left = left + this._$px;
        handle.top = top + this._$px;
    
	}

},


// Handle a native scroll event

_$nativeScroll: "nativeScroll",
_handleCSSScroll : function (waited, fromFocus) {
    if (isc._traceMarkers) arguments.__this = this;

    // The contents of the Canvas have already been scrolled by the browser, and we're just
    // being notified of it.

    
    if (this._scrollingHandleDirectly) return;

    // Avoid attempting to handle a delayed scroll if the widget in question has been cleared
    if (!this.isDrawn()) return;

    
    var scrollMechanism = this.getScrollingMechanism();
    if (scrollMechanism != isc.Canvas.NATIVE) {
        this.logWarn("unsupported native scroll occurred on this widget - resetting");
        var handle = this.getScrollHandle();
        if (scrollMechanism == isc.Canvas.NESTED_DIV) {
            this._scrollHandle(this.isRTL() ? this.getScrollRight() : 0, 0);
        } else {
            this._scrollHandle(this.scrollLeft, this.scrollTop);
        }
        return;
    }

    //>Moz
    
    if (isc.Browser.isMoz && !waited && (fromFocus ||  isc.Browser.geckoVersion < 20030312)) {
        if (!this._scrollTimeout)
            this._scrollTimeout = this.delayCall("_handleCSSScroll", [true], 10);
        return;
    }
    this._scrollTimeout = null;
    //<Moz

    var trueScrollLeft = this.getScrollLeft(),
        trueScrollTop = this.getScrollTop();

    // if our notion of the scroll position matches the DOM's - just return
    if (trueScrollLeft == this.scrollLeft && trueScrollTop == this.scrollTop) return;

    // Even though the native element has already scrolled, we call scrollTo to update
    // this.scrollLeft/Top, and to cause any scrollTo overrides or observations to fire
    

    isc.EH._setThread("SCR");
    this.scrollTo(trueScrollLeft, trueScrollTop, this._$nativeScroll);
    isc.EH._clearThread();
},


mouseWheel : function () {
    if ((this.overflow == isc.Canvas.AUTO || this.overflow == isc.Canvas.SCROLL) &&
        this.showCustomScrollbars && this.vscrollOn)
    {
        var wheelDelta = this.ns.EH.lastEvent.wheelDelta;

        // For each increment the user scrolled the mouse wheel, we want to move about 50px
        // This seems to approximately match native scrolling speed.
        var scrollTo = this.scrollTop + Math.round(wheelDelta * isc.Canvas.scrollWheelDelta);

        // Note that scrollTo already catches scrolling past beginning or end
        this.scrollTo(this.getScrollLeft(), scrollTo, "mouseWheel");

        // return false to cancel further / native processing
        return false;
    }

    // Not a scrollable region, return true
    return true;
},

// Helpers to determine if this was a 'fast' scrolling mechanism - track scroll / drag scroll
isDragScrolling : function () {
    if (this.vscrollOn && this.vscrollbar && this.vscrollbar.isDragScrolling()) return true;
    if (this.hscrollOn && this.hscrollbar && this.hscrollbar.isDragScrolling()) return true;
    return false;
},

isRepeatTrackScrolling : function () {
    if (this.vscrollOn && this.vscrollbar && this.vscrollbar.isRepeatTrackScrolling()) return true;
    if (this.hscrollOn && this.hscrollbar && this.hscrollbar.isRepeatTrackScrolling()) return true;
    return false;
},

isMouseWheelScrolling : function () {
    return isc.EH.lastEvent.eventType == isc.EH.MOUSE_WHEEL;
},

// Default Keyboard Handling
// --------------------------------------------------------------------------------------------
// Canvases have built-in scrolling and focus change behavior for keyboard events

//>	@method	canvas.handleKeyPress()     (A)
//  Canvas level handler function for the (possibly bubbled) keyPress event, fired by the event
//  handling system when the user presses a key on a focus'd widget.
//  Fires any user-defined 'keyPress' handler string method.
//  Scrolls the widget on arrow keypresses
//      @group  events
//
//      @param  event   (ISC Event object)
//      @param  eventInfo   (object)
//<

handleKeyPress : function (event, eventInfo) {
    var keyPressReturn;

    // If a keypress string method handler is defined, call it before firing standard scrolling
    // logic
    if (this.convertToMethod("keyPress")) {
        keyPressReturn = this.keyPress(event, eventInfo);
    }

    
    if (keyPressReturn != false && this.shouldCancelKey != null  &&
        this.shouldCancelKey(event, eventInfo))
    {
        keyPressReturn = false;
    }

    if (keyPressReturn == false) return false;

    var keyName = event.keyName;
    
    if (this._useFocusProxy &&
        ((isc.Browser.isMoz && this.canSelectText) || isc.Browser.isSafari)
        && keyName == "Tab")
    {
        this.setFocus(true);
    }


    // if using custom scrollbars, scroll if standard scrolling keys are hit
    
    if ((this.overflow == isc.Canvas.AUTO || this.overflow == isc.Canvas.SCROLL) &&
         this.showCustomScrollbars)
    {

        keyPressReturn = this.handleKeyboardScroll(keyName);
    }
    return keyPressReturn;
},

handleKeyboardScroll : function (keyName) {
        var leftDelta = 0, topDelta = 0;

        // pageUp/Down: scroll one viewport
        if (keyName == "Page_Up") topDelta -= this.getViewportHeight();
        else if (keyName == "Page_Down") topDelta += this.getViewportHeight();
        // arrows: scroll a small amount
        else if (keyName == "Arrow_Up") topDelta -= 10; // Maybe make this a var?
        else if (keyName == "Arrow_Down") topDelta += 10;
        else if (keyName == "Arrow_Left") leftDelta -= 10;
        else if (keyName == "Arrow_Right") leftDelta += 10;

        var reason = "cancel native keyPress scrolling";
        if (leftDelta != 0 || topDelta != 0) {
            // NOTE: scrollTo automatically clamps
            this.scrollTo(this.scrollLeft + leftDelta, this.scrollTop + topDelta, reason);

            // return false so the event doesn't get propagated
            return false;
        }

        // Home / End - go to the top or bottom
        if (keyName == "Home") {
            this.scrollTo(null, 0, reason);
            return false;
        } else if (keyName == "End") {
            this.scrollTo(null, (this.getScrollHeight() - this.getViewportHeight()), reason);
            return false;
        }
},

handleKeyDown : function (event,eventInfo) {
    // If a keyDown string method handler is defined, call it before firing standard scrolling
    // logic
    var keyDownReturn
    if (this.convertToMethod("keyDown")) {
        keyDownReturn = this.keyDown(event, eventInfo);
    }
    return keyDownReturn;
},

// --------------------------------------------------------------------------------------------

//>	@method	canvas._setHandleRect()	(A)
//			(internal) routine to set the rectangle of the canvas handle to its
//          .left, .top, .width, .height
//		@group	positioning, sizing
//
//		@param	left	(number)
//		@param	top		(number)
//		@param	width	(number)
//		@param	height	(number)
//<


_setHandleRect : function (left, top, width, height) {
    // Bail if we're not actually drawn (have no handle to position)
    var styleHandle = this.getStyleHandle();
    if (styleHandle == null) return;
    
    

    

    // In RTL mode, we need to shift the handle to the right of the v-scrollbar if appropriate
    if (this.showCustomScrollbars && this.vscrollOn && left != null && this.isRTL()) {
        left += this.getScrollbarSize();
        //this.logWarn("adjusting left coordinate for RTL:" + left + this.getStackTrace());
    }

    // Call the '_adjustHandleSize' helper method to determine the width/height
    // values we will actually apply to the handle to get the desired size.
    // This corrects for the space required by custom scrollbars, and for border, padding, etc.
    if (width != null || height != null) {

        var adjustedSize = this._adjustHandleSize(width, height);

        width = adjustedSize[0];
        height = adjustedSize[1];
    }
    //this.logWarn("assigning size of: " + [width, height]);

        if (left != null && isc.isA.Number(left)) this._assignSize(styleHandle, isc.Canvas.LEFT, left);
        if (top != null && isc.isA.Number(top)) this._assignSize(styleHandle, isc.Canvas.TOP, top);
        if (width != null && isc.isA.Number(width)) this._assignSize(styleHandle, this._$width,
                                                                     Math.max(width,1));
        if (height != null && isc.isA.Number(height)) this._assignSize(styleHandle, this._$height,
                                                                       Math.max(height,1));
        //  this.logWarn("setHandleRect: style handle now reports: " +
        //              this.echo({left:styleHandle.left, top:styleHandle.top,
        //                         width:styleHandle.width, height:styleHandle.height}));
},

_$px : "px",
_assignSize : function (styleHandle, prop, size) {
    if (isc.Browser.isIE || isc.Browser.isOpera) {
        if (!isc.Browser.isStrict) {
            
            styleHandle[prop] = size;
        } else {
            // strict mode IE JS errors if a negative height or width is set
            if (size < 0 && (prop == this._$width || prop == this._$height)) size = 0;
            styleHandle[prop] = size + this._$px;
        }
    } else {
        if (styleHandle == null) {
            
            //this.logWarn(" size: " + size + ", styleHandle is: " + styleHandle);
            return;
        }
        var propVal = size + this._$px;
        styleHandle[prop] = propVal;
        if (styleHandle.setAttribute != null) {
            
            styleHandle.setAttribute(prop, propVal);
        }
    }
},

_sizeBackMask : function () {
    var backMask = this._backMask;
    if (!backMask) return;
    if (this.showEdges) {
        // keep the backmask from "squaring out" edges that use transparency
        var edge = this._edgedCanvas,
            // If maskEdgeCenterOnly is set, mask only the area of the center segment of the
            // edges.  Any content that overlaps the edges will still burn through.  This
            // allows a translucent Window header and other uses cases, with the drawback that
            // those areas won't be masked.
            center = this.maskEdgeCenterOnly,
            left = center ? edge._leftEdge : edge._leftMargin,
            right = center ? edge._rightEdge : edge._rightMargin,
            top = center ? edge._topEdge : edge._topMargin,
            bottom = center ? edge._bottomEdge : edge._bottomMargin,
            width = this.getVisibleWidth() - (left + right),
            height = this.getVisibleHeight() - (top + bottom);
        // NOTE: this can happen when the edgedCanvas is a background and the widget sizes
        // so that only edges are visible, eg rounded minimized window
        if (width <= 0 || height <= 0) backMask.hide();
        else {
            if (this.isVisible()) backMask.show();
            //this.logWarn("sizing backmask to: " + [width, height] + this.getStackTrace());
            backMask.setRect(this.getLeft() + left,
                             this.getTop() + top,
                             width,
                             height);
        }
    } else {
        backMask.setRect(this.getRect());
    }
},

// Text Direction
// --------------------------------------------------------------------------------------------
// Bi-directional text (BIDI) support for languages that read right to left (RTL)



//>	@method	canvas.getTextDirection()
//		Get the text direction of this canvas.
//		This property is determined according to the containment hierarchy
//		 (like disabled) and is ultimately set by the page property if
//		 not defined by any widget.
//
//		@group	appearance
//		@platformNotes	IE only.
//		@return	(TextDirection)	direction -- Canvas.LTR or Canvas.RTL
// @visibility internal
//<

getTextDirection : function () {
    if (this._textDirection) return this._textDirection;

	// start off by looking in this object
	var target = this;

	// while the target exists
	while (target) {
	    // if the is not enabled, return false
		if (target.textDirection != null) {
            return (this._textDirection = target.textDirection);
        }
		// otherwise look up the parent chain
		target = target.parentElement;
		// and if an eventProxy is defined, use that instead of the parentElement
		if (target && target.eventProxy) target = target.eventProxy;
	}
	// if no widget specified a textDirection, use the Page.textDirection
	return (this._textDirection = isc.Page.getTextDirection());
},

//> @method canvas.isRTL()
// Return whether the text direction is right to left
// @return (boolean) whether text direction is RTL
//<
isRTL : function () {
    return (this.getTextDirection() == isc.Canvas.RTL);
},

//> @method canvas.getRTLSign()
// Get either one or negative one if the text direction is LTR or RTL respectively.
// <P>
// Useful for writing LTR/RTL component layout algorithms.
//
// @return (Number) 1 for LTR, -1 for RTL
//<
getRTLSign : function () {
    return this.isRTL() ? -1 : 1;
},

// Visibility
// ------------------------------------------------------------------------------------------------
// Whether this Canvas is currently visible

//>	@method	canvas.setVisibility()	(A)
//			set the visibility of this object
//		@group	visibility
//
//		@param	newVisibility 	(string) 	CSS visibility to set to (Canvas.HIDDEN,
//		                                    Canvas.VISIBLE, etc)
//								(boolean)	false == hide, anything else == show
//<
setVisibility : function (newVisibility) {
    //>Animation
    // Finish any hide/show animations that are running
    
    if (this._animatingHide != null && !this._hidingAsAnimation)
        this.finishAnimation(this._animatingHide);
    if (this._animatingShow != null && !this._showingAsAnimation)
        this.finishAnimation(this._animatingShow);
    // if we have any opacity change animation(s) running, finish them before changing our
    // visibility
    if (this.fadeAnimation) this.finishAnimation("fade");
    //<Animation

	// if newVisibility is a boolean, normalize to a CSS value
	if (!isc.isA.String(newVisibility)) {
        newVisibility = (newVisibility != false ? isc.Canvas.INHERIT : isc.Canvas.HIDDEN);
    }

    // no-op if no change in visibility
    if (this.visibility == newVisibility) return;

    // check if we're currently visible (NOTE: must call isVisible to check parents)
    var wasVisible = this.isVisible();

	// set the visible state of the object
	this.visibility = newVisibility;

	// if we're drawn
	if (this.isDrawn()) {

		if (!wasVisible && this.isVisible()) {

            // If we're showing a widget that is awaiting redraw, or has a child awaiting redraw,
            // redraw before showing to avoid a flash after show().
            if (this.isDirty()) {
                this.redraw("show() while dirty");

            } else if (this.children && this.children.length > 0) {

                // check for dirty children and redraw them.  Use the redraw queue to determine
                // whether we have any dirty children - quicker than iterating down through all
                // our children recursively.

                // Make a copy of the Queue, as redrawing widgets inside it will change it's
                // length, etc.
                var origRedrawQueue = isc.Canvas._redrawQueue.duplicate();

                // Note - we redraw any children directly rather than just redrawing the
                // parent, because it's more efficient -- while the parent is likely to have
                // no significant content, redrawing it could force a redraw of a number of
                // siblings
                for (var i = 0; i < origRedrawQueue.length; i++) {
                    var widget = origRedrawQueue[i];
                    // If we're the parent of a dirty child, redraw it.
                    // the isDirty() check verifies that we haven't cleaned it up by redrawing
                    // a parent in a prev iteration of this loop.
                    // Note - it's ok to leave the widget in the redraw queue, as
                    // clearRedrawQueue() will skip any widgets that are no longer dirty
                    if (widget && widget.isDirty() && this._isVisibilityAncestorOf(widget)) {
                        widget.redraw("show() on parent while dirty");
                    }
                }
            }

		}

        this._setHandleVisibility(newVisibility);

        // Update handle.display if using hideUsingDisplayNone
        
        this._updateHandleDisplay();
    }

	// if we have peers, show or hide them as well
	if (this.peers) {
		for (var i = 0; i < this.peers.length; i++) {
            var peer = this.peers[i];
            // special case the scrollbars: they should generally hide and show with the
            // master, but sometimes we hide a scrollbar because we no longer need to scroll on
            // that axis, and we don't want it to get show()n when we show().
            if (this.isVisible() &&
                ((peer == this.hscrollbar && !this.hscrollOn) ||
                 (peer == this.vscrollbar && !this.vscrollOn))) continue;
            // don't show the shadow if we're set to no longer have one (eg temporary drag
            // shadow)
            if (this.isVisible() && peer == this._shadow && !this.showShadow) continue;
            if (peer._showWithMaster) peer.setVisibility(newVisibility);
		}
	}

    // notify children that visibility changed
    if (this.children) this.children.map("parentVisibilityChanged", newVisibility, this);

    if (this.parentElement) this.parentElement.childVisibilityChanged(this, newVisibility);

    //>FocusProxy
    // If we have a 'focusProxy' make sure it has the appropriate visibility
    if (this._useFocusProxy) this._updateFocusProxyVisibility();
    //<FocusProxy

    this._visibilityChanged();
},

// Fires the visibilityChanged notification if appropriate
// documented in registerStringMethods
_visibilityChanged : function () {
    if (!this.isDrawn()) return;
    // Set a flag tracking this.isVisible() so we only fire when
    // the actual visibility to the user changes.
    // This flag is always re-initialized on draw()
    var visible = this.isVisible();
    if (visible != this._currentlyVisible) {
        this._currentlyVisible = visible;
        if (this.visibilityChanged != null) {
            this.visibilityChanged(this.isVisible());
        }
    }
},

// tell our children some parent's visibility changed
parentVisibilityChanged : function (newVisibility, parent) {
    if (this.children) this.children.map("parentVisibilityChanged", newVisibility, parent);

    
    this._updateHandleDisplay();

    //>EditMode if we had the resize thumbs, hide them
    if (this == isc.Canvas._thumbTarget) isc.Canvas.hideResizeThumbs();
    //<EditMode

    // If we have a 'focusProxy', make sure it has the appropriate visibility
    if (this._useFocusProxy) this._updateFocusProxyVisibility();

    // this._visibilityChanged() verifies that this.isVisible() actually changed
    // minor optimization: this can only happen if the parent was a visibility ancestor of this widget
    if (parent._isVisibilityAncestorOf(this)) this._visibilityChanged();
},

// notification that a child's visibility changed
childVisibilityChanged : function (child, newVisibility) {
    // NOTE: if a child uses display:none or some other custom way of hiding itself, this
    // *might* reduce content size.
    this._markForAdjustOverflow("childVisChange");
},

// notifications that a child/peer was cleared
childCleared : function (child) { if (!this.destroying) this._markForAdjustOverflow("childClear"); },
peerCleared : function (peer) { },

childDrawn : function (child) {
    if (this.isDrawn()) this._markForAdjustOverflow("childDraw")
},
peerDrawn : function (peer) {},

//>FocusProxy
// If this widget has a 'focusProxy' - ensure it is shown and hidden with the widget

_updateFocusProxyVisibility : function () {
    if (!this._useFocusProxy || !this._hasFocusProxy) return;

    var isVisible = this.isVisible(),
        proxy = this._getFocusProxyHandle();
    if (proxy) {
        if (isVisible && proxy.style.visibility == isc.Canvas.HIDDEN)
            proxy.style.visibility = isc.Canvas.VISIBLE
        if (!isVisible && proxy.style.visibility != isc.Canvas.HIDDEN)
            proxy.style.visibility = isc.Canvas.HIDDEN
    }
},
//<FocusProxy

//>	@method	canvas._setHandleVisibility()	(A)
//			(internal) routine to set the visibility of the underlying DOM element.  Call
//			           setVisibility instead.
//		@group	visibility
//
//		@param	newVisibility 	(string) 	CSS visibility constant
//<
_setHandleVisibility : function (newVisibility) {
    var handle = this.getStyleHandle();
    if (handle != null) handle.visibility = newVisibility;

},

_$none:"none",
_updateHandleDisplay : function () {
    if (!this.hideUsingDisplayNone || !this.isDrawn()) return;

    var handle = this.getStyleHandle();

    if (!this.isVisible() && !this._setToDisplayNone) {
        // save off the current state of the display property so we can restore it when the
        // component becomes visible again
        this._visibleDisplayStyle = handle.display;
        this._setToDisplayNone = true;
        handle.display = this._$none;

        
        this._$leftCoords = this._$topCoords = null;

    } else if (this.isVisible() && this._setToDisplayNone) {
        // if the display property had a value other than the empty string when it was in
        // visible state (picked up when we hide) we use that
        handle.display = (this._visibleDisplayStyle ? this._visibleDisplayStyle :
                                                      isc.emptyString);
        delete this._setToDisplayNone;
        delete this._visibleDisplayStyle;
    }
},

// Helper method: should we draw() on a call to show()?
// Returns true for top level widgets that are not peers
// (in which cases parent/master is responsible for drawing at the right time).
// Also, don't draw if we are drawn, or already in the middle of drawing
// (This can happen if a child tries to show it's parent while the parent is drawing it).
_drawOnShow : function () {
    return (this.getDrawnState() == isc.Canvas.UNDRAWN) &&
           !this.parentElement && !this.masterElement;
},

//>	@method	canvas.show()   ([])
// Sets this widget's visibility to "inherit", so that it becomes visible if all of its parents
// are visible or it has no parents.
// <P>
// If the widget has not yet been drawn (and doesn't have a parent or master), this method
// calls the draw method as well.
//
//      @visibility external
//		@group	visibility
//      @example    showAndHide
//<
show : function () {
    if (isc._traceMarkers) arguments.__this = this;

    var showWithFocus = this.hasFocus;

	// if we haven't yet been drawn, go ahead and do so for top level widgets
    if (this._drawOnShow()) {
        // note: passing the parameter to draw will prevent draw calling show() again when it is
        // done drawing.
        this.draw(true);
	}

	this.setVisibility(isc.Canvas.INHERIT);

    if (showWithFocus && this.hasFocus) {
        //>DEBUG if we were marked as having focus before being drawn, and still are, focus
        // explicitly
        this.logInfo("Show: Hidden / Undrawn widget marked as having focus - calling focus()",
                     "events"); //<DEBUG
        
        this.hasFocus = false;
        this.focus();
    }

    if (this.autoShowParent && this.parentElement) this.parentElement.show();
},

//> @method canvas.showRecursively()   ([])
// Recursively show the canvas and all it's parents so the canvas will be visible.
// <P>
// If the widget has not yet been drawn, this method calls the draw method as well.
//
//      @visibility external
//      @group  visibility
//<
showRecursively : function () {
    var parent = this.parentElement;
    if (this._containerID == null && parent == null) {
        this.show();
    } else {
        this.setVisibility(isc.Canvas.INHERIT);
        if (this._containerID != null) {
            parent = window[this._containerID];
        }
        parent.showRecursively();
        if (isc.TabSet != null && isc.isA.TabSet(parent)) {
            parent.selectTab(parent.tabForPane(this));
        } else if (isc.SectionStack != null && isc.isA.SectionStack(parent)) {
            parent.expandSection(parent.sectionForItem(this));
        }
    }
},

_$relative:"relative",
_relativePageResized : function () {
    if (!this.isDrawn() || this.parentElement || this.position != this._$relative) return;

    // Fire completeMoveBy() - this will handle all our notifications such as
    // masterMoved() on our peers.
    var oldLeft = this._preResizePageLeft,
        oldTop = this._preResizePageTop,
        pageLeft = this.getPageLeft(),
        pageTop = this.getPageTop();

    this._moveDeltaX = (pageLeft - oldLeft);
    this._moveDeltaY = (pageTop- oldTop);
    this._completeMoveBy();

    // re-register for the next resized event
    this._preResizePageLeft = pageLeft;
    this._preResizePageTop = pageTop;
    isc.Page.setEvent(
        "resize",
        this,
        isc.Page.FIRE_ONCE,
        "_relativePageResized"
    );


},


//>	@method	canvas.hide()   ([])
//			Sets the widget's CSS visibility attribute to "hidden".
//      @visibility external
//		@group	visibility
//      @example    showAndHide
//<
hide : function () {
    this._updateFocusForHide();
	this.setVisibility(isc.Canvas.HIDDEN);
},

//>	@method	canvas.isVisible()  ([])
// Returns true if the widget is visible, taking all parents into account, so that a widget
// which is not hidden might still report itself as not visible if it is within a hidden
// parent.
// <P>
// NOTE: Undrawn widgets will report themselves as visible if they would be visible if drawn.
//
//      @visibility external
//		@group	visibility
//
//		@return	(boolean)	true if the widget is visible, false otherwise
//<
isVisible : function () {
	// start off by looking in this object
	var target = this;

	// while the target exists
	while (target) {
		// if the is not visible, return false
		if (target.visibility == isc.Canvas.HIDDEN) return false;

		// if the is explicitly set as visible, return true
		if (target.visibility == isc.Canvas.VISIBLE) return true;

		// otherwise "inherit", so look up the parent chain
		target = target.parentElement;
	}
	// if everyone is inheriting visiblility (up to the page itself), return true
	return true;
},

// _isDisplayNone()
// Internal method - returns true if this canvas or any of its ancestors is currently rendered
// with display:"none"
_isDisplayNone : function () {
    var target = this;
    while (target) {
        if (target.visibility == isc.Canvas.HIDDEN && target.hideUsingDisplayNone) return true;
        target = target.parentElement;
    }
    return false;
},


// Enable/Disable
// -----------------------------------------------------------------------------------------------
//>	@groupDef enable
// Disabled components do not respond to mouse or keyboard events, and change appearance to
// indicate they are disabled.
// @title Enabling and Disabling
// @visibility external
//<

//> @method canvas.setEnabled()     (A)
// set the enabled state of this object.
//
// @group enable
// @param  newState (boolean) pass false to disable or anything else to enable
// @visibility external
// @deprecated As of Smartclient version 5.5, deprecated in favor of +link{canvas.setDisabled()}
//<
_$disabled:"disabled",
setEnabled : function (newState) {
    this.logWarn("call to deprecated method 'setEnabled()' - use 'setDisabled()' instead."
                
    );
    var disabled = ((newState == null || isc.isA.Boolean(newState)) ? !newState
                    : (newState == this._$disabled));

    this.setDisabled(disabled);
},

//>	@method	canvas.setDisabled()	(A)
// set the disabled state of this object
// @group enable
// @param disabled (boolean) new disabled state of this object - pass <code>true</code> to disable the widget
// @visibility external
//<
setDisabled : function (newState, dontNoOp) {

    // We can no-op if we're already explicitly set to the appropriate state.
    if (newState == null) newState = false;
    if (!isc.isA.Boolean(newState)) newState = (newState == this._$disabled);
    if (this.disabled == newState) return;


	// Notify peers that the master element has been disabled
    // Will disable the peers too where appropriate
	if (this.peers) this.peers.map("masterDisabled", newState);

    // disabled state is inherited in a similar way to hidden visibility - if any of this widgets
    // ancestors are disabled, it's considered disabled, even if the "disabled" property has
    // been set to true.
    // Use isDisabled() to check whether this state change needs to have any effect on the
    // widget's handle and its children.
    var wasDisabled = this.isDisabled()
	// set the disabled state of the object
	this.disabled = newState;
    var isDisabled = this.isDisabled();

    if (wasDisabled != isDisabled || dontNoOp) {

        // update our HTML to reflect a change of state.
        this.setHandleDisabled(isDisabled);

        // If we have any children, they will also be affected by the state change.
        if (this.children) this.children.map("parentDisabled", isDisabled);
    }
},

// Notification function called when a widget's masterElement gets disabled
masterDisabled : function (disabled) {
    // Simply update our disabled state to match our masters, since we don't actually inherit
    // enabled /disabled state from our master like children do.
    this.setDisabled(disabled);
},

// notification function fired when a widget's parent or ancestor gets disabled or enabled

parentDisabled : function (disabled) {
    // If we're explicitly disabled the parent's enabling/disabling will not effect us - we
    // will remain disabled
    if (this.disabled) return;

    // If the parent redrew on being disabled we don't need to update our HTML
    if (!this.parentElement.redrawOnDisable) this.setHandleDisabled(disabled);

    // Tell our descendants about the change
    if (this.children) this.children.map("parentDisabled", disabled);
},


// Helper to actually update our HTML for becoming disabled / enabled
setHandleDisabled : function (disabled) {
    if (!this.isDrawn()) return;

    if (this.redrawOnDisable) this.markForRedraw("setDisabled");

    if (this._canFocus()) this.disableKeyboardEvents(disabled);


},

disableKeyboardEvents : function (disabled, recursive) {

    if (disabled) {
        // these methods will No-Op if this._useNativeTabIndex is false, or if the widget
        // is not drawn.
        this._setHandleTabIndex(-1);
        if (this.accessKey != null) this._setHandleAccessKey(null);
    } else {
        // restore them when we're enabling them!
        this._setHandleTabIndex(this.getTabIndex());
        if (this.accessKey != null) this._setHandleAccessKey(this.accessKey);
    }

    // On disable, do a blur
    // Do this even if we're redrawing since that's done asynchronously
    if (disabled && this.hasFocus) this.blur();

    if (recursive && this.children) {
        for (var i = 0; i < this.children.length; i++) {
            this.children[i].disableKeyboardEvents(disabled, true);
        }
    }
},

//>	@method	canvas.enable() ([])
// Enables this widget and any children / peers of this widget.
//  @visibility external
//  @group enable
//<
enable : function () {

	// if the object is not already enabled
	if (this.disabled) this.setDisabled(false);
},


//>	@method	canvas.disable()    ([])
// Disables this widget and any children and peers of this widget.
//  @visibility external
//  @group enable
//<
disable : function () {
	if (!this.disabled) this.setDisabled(true);
},


//>	@method	canvas.isDisabled()  ([])
// Is this canvas disabled? Note that the disabled state is inherited - this method will return
// true if this widget, or any of its ancestors are marked disabled.
//  @visibility external
//  @group enable
//  @return	(Boolean)   true if the widget or any widget above it in the containment hierarchy
//                      are disabled.
//<
isDisabled : function () {
    // Check this widget and each ancestor of it for the 'disabled' property
	var target = this;
	while (target) {
		if (target.disabled) return true;

		target = target.parentElement;
		// and if an eventProxy is defined, use that instead of this object
		if (target && target.eventProxy) target = target.eventProxy;
	}
	// if no-one is disabled, return false
	return false;
},

//> @method canvas.isEnabled()  ([])
// Returns true if the widget and all widgets above it in the containment hierarchy are enabled.
// Returns false otherwise.
// @visibility external
// @group enable
// @return    (boolean)   true if the widget and all widgets above it in the containment hierarchy
//                      are enabled; false otherwise
// @deprecated As of SmartClient version 5.5 deprecated in favor of +link{canvas.isDisabled()}.
//<
isEnabled : function () {
    this.logWarn("Call to deprecated 'isEnabled()' method - should use isDisabled() instead");
    return !this.isDisabled();
},

// Focus
// --------------------------------------------------------------------------------------------
//> @groupDef focus
// Focus is the ability to become the target of keyboard input events.
// <P>
// A widget normally receives focus by being clicked on or tabbed to.
//
// @title Focus
// @visibility external
//<

// Internal method to determine whether this widget should accept keyboard focus.
_canFocus : function () {
    // respect explicit setting
    if (this.canFocus != null) return this.canFocus;

    // If this.canFocus is not set, allow focus only on scrollable canvii with visible
    // scrollbars
    if ((this.overflow == isc.Canvas.SCROLL) ||
        ((this.overflow == isc.Canvas.AUTO) && (this.vscrollOn || this.hscrollOn)) ) {
            return true;
    }

    return false;
},

_shouldManageTabIndex : function () {
    if (this._canFocus()) return true;
    return !!this._hasTabDescendents
},

//>	@method	canvas.setCanFocus()	(A)
// Change whether a widget can accept keyboard focus.
// @param canFocus (boolean) whether the widget should now accept focus
// @see canFocus
// @visibility external
//<
setCanFocus : function (canFocus) {
    this.canFocus = canFocus;
    this._updateCanFocus();
},

// Internal method to update the widget's handle to allow / disallow keyboard focus, based on
// the result of this._canFocus()
// Will No-Op if we're not using native tab-index / focus behavior, either by writing handlers
// onto the widget handle, or using the focusProxy approach.
_updateCanFocus : function () {
    this._updateHandleForFocus(this._canFocus());
    this.canFocusChanged();
},

_updateHandleForFocus : function (canFocus) {
    // If we're using native tab index (tabIndex and accessKey properties written into the widget
    // handle), we need to clear them out / update them.
    // This means updating the the handle's tabIndex / onfocus/onblur handler and accessKey.
    //
    // If we're using a focusProxy DOM element, we need to update (or create / destroy) it.
    //
    // If we're not leveraging any native browser tabIndex behavior, but a widget is being
    // removed from the tab-order, we need to clear up it's references in the auto-assigned tab
    // index system.
    var handle;

    //>FocusProxy if we're using a focusProxy, make sure it's present or absent as necessary
    if (this._useFocusProxy) {
        if (canFocus) {
            handle = this._getFocusProxyHandle();

            // if the focusProxy doesn't exist, call the makeFocusProxy method to create it.
            // Notes:
            // - the handle will be created in the correct state, so we return
            // - creation is delayed in some browsers
            if (!handle) return this.makeFocusProxy();
        } else {
            // we shouldn't be focusable, so destroy our focusProxy if we have one, and remove
            // ourselves from the tab order
            this._clearFocusProxy();
            
            return;
        }

        // we're focusable and we already have a focusProxy.
        // In Safari - when tabIndex is -1 we don't write a focusProxy into the DOM.
        // If tabIndex is -1 and we already have a focus proxy, clear it here
        
        if (isc.Browser.isSafari && this.getTabIndex() == -1) {
            this._clearFocusProxy();
            return;
        }
    }
    //<FocusProxy

    
    if (this._useAccessKeyProxy()) {
        if (canFocus && this.accessKey) {
            this._makeAccessKeyProxy();
        } else if (this._accessKeyProxy) {
            this._clearAccessKeyProxy();
        }
    }

    // If we're writing focus properties directly onto the widget handle, get a pointer to that
    // for manipulation below.
    if (this._useNativeTabIndex) handle = this.getFocusHandle();

    if (canFocus) {
        // Note: this.getTabIndex() will set this._autoTabIndex as appropriate.
        // _setTabIndex() will fall through to _setHandleTabIndex() and apply the tabIndex to
        // the handle if appropriate.
        //
        
        this._setTabIndex(this.getTabIndex(), this._autoTabIndex);

        // if we can accept focus, setup handlers and put us in the tab order
        if (handle != null) {
            
            var focusHandler = this._getNativeFocusHandlerMethod(),
                blurHandler = this._getNativeBlurHandlerMethod();
            handle.onfocus = focusHandler;
            handle.onblur = blurHandler;
            if (this.accessKey) this._setHandleAccessKey(this.accessKey);
        }


    } else {
        // if we can't accept focus, clear handlers and remove us from the tab order
        if (handle != null) {
            handle.onFocus = null;
            handle.onBlur = null;

            // Remove from the tab order
            
            this._setHandleTabIndex(-1);

            if (handle.accessKey != null) this._setHandleAccessKey(null);
        }
        
    }
},

// notification fired when the canFocus status of this widget has updated
canFocusChanged : function () {
    var parent = this.parentElement;
    while (parent) {
        parent.childCanFocusChanged(this);
        parent = parent.parentElement;
    }
},
// Notification fired when the canFocus status of some child of this widget changes
// Fired by 'canFocusChanged'.  No Op by default.

childCanFocusChanged : function (child) {
},

// update whether or not we should show the focusOutline
// handleOnly param will update the handle to show / hide the focus outline but leave
// this.showFocusOutline unmodified
_$none:"none",
setShowFocusOutline : function (showFocusOutline, handleOnly) {
    if (!handleOnly && this.showFocusOutline == showFocusOutline) return;
    if (!handleOnly) this.showFocusOutline = showFocusOutline;
    if (isc.Browser.isMoz) {
        var handle = this.getClipHandle();
        if (handle) {
            handle.style.MozOutlineStyle = (showFocusOutline ? isc.emptyString : this._$none);
        }
    } else {
        var handle = this.getHandle();
        if (handle) handle.hideFocus = !showFocusOutline;
    }

},

////////
// obtain or lose the focus in this object
//
//	if this object wants to get the focus when it's clicked, set:
//		obj.canFocus = true
//
//	to have an object redraw when its focus changes, set:
//		obj.redrawOnFocus = true
////////

//_readyToSetFocus: can we update the focus state of this widget?
_readyToSetFocus : function (focus) {
    
    
    return (this.isDrawn() && this.visibleInDOM() && (!focus || !this.isDisabled()));
},

visibleInDOM : function () {
    if (!this.isVisible()) return false;

    // relative positioned widgets can be hidden without us ever being notified of the hide,
    // if the ancestors in the DOM have been hidden
    // If the topParent is pos:"relative", iterate up from the topParent to the page body and verify
    // all elements are visible
    
    var topWidget = this;
    while (topWidget.parentElement) topWidget = topWidget.parentElement;

    if (topWidget.position == isc.Canvas.ABSOLUTE) return true;

    var docBody = this.getDocumentBody();
    var handle = topWidget.getClipHandle().parentNode;
    while (handle && handle != docBody) {
        var style = handle.style;
        if (style && style.visibility == this._$hidden) return false;
        if (style && style.display == this._$none) return false;
        handle = handle.parentNode;
    }
    return true;
},

// get the handle this widget uses for focus, if there is one
getFocusHandle : function () {
    if (this._useNativeTabIndex) {
        return this.getClipHandle();
    //>FocusProxy
    } else if (this._useFocusProxy && this._hasFocusProxy) {
        return this._getFocusProxyHandle();
    //<FocusProxy
    }
    return null;
},

//>	@method	canvas.setFocus()	(A)
// set the focused state of this object
//		@group	focus
//
//	@param	newState			(boolean) pass false to blur or anything else to focus
//<
setFocus : function (newState, reason) {
    if (!this._readyToSetFocus(newState)) return;
    var focusHandle = this.getFocusHandle(newState);

    

    // call the EventHandler method to update the current focus canvas
    if (newState && this._canFocus()) {

        // If the widget is currently masked we should avoid calling handle.focus(), and instead
        // notify the EH directly so it can update it's 'maskedFocusCanvas'
        // Because of IE's fun asynchronous focus handler behavior we'll have to also catch the
        // case where focus has previously been called on another widget, but the focus handler
        // hasn't been fired yet and avoid it clobbering this maskedFocusCanvas setting when
        // onfocus does fire.

        if (focusHandle != null) {
            /*
            if (isc.EH._unconfirmedFocus == this) {
                // aborting here has not yet been observed to matter
                this.logDebug("ignoring focus attempt on widget with unconfirmed focus()");
                return;
            }
            */
            
            if (isc.Browser.isIE && document.activeElement == focusHandle) {
                this.logInfo("setFocus() not calling element.focus() as element already has " +
                    "native focus", "nativeFocus");
            } else {
                //>DEBUG
                this.logInfo("about to call native focus()" +
                             (this.logIsDebugEnabled("traceFocus") ? this.getStackTrace() : ""),
                             "nativeFocus");
                //<DEBUG
                
                isc.EH._unconfirmedFocus = this;
                focusHandle.focus();
                isc.EH._lastFocusTarget = this;
            }
        } else {
            this.ns.EH.focusInCanvas(this);
        }

    } else if (this.hasFocus) {
        if (focusHandle) {
            /*
            if (isc.EH._unconfirmedBlur == this) {
                // aborting here has not yet been observed to matter
                this.logWarn("ignoring blur attempt on widget with unconfirmed blur()");
                return;
            }
            */
            //>DEBUG
            this.logInfo("about to call native blur()" +
                         (this.logIsDebugEnabled("traceBlur") ? this.getStackTrace() : ""),
                         "nativeFocus");
            //<DEBUG
            
            isc.EH._unconfirmedBlur = this;
            focusHandle.blur();
        } else {
            this.ns.EH.blurFocusCanvas(this);
        }
    }

    
},


_restoreFocus : function () {
    // abort if focus has moved (onFocus() fired somewhere else)
    var focusCanvas = isc.EH._focusCanvas;
    if (focusCanvas != null && focusCanvas != this) {
        this.logDebug("not restoring focus; focus moved to: " + focusCanvas,
                      "nativeFocus");
        return;
    }
    // abort if we've called native focus() to move focus elsewhere, but onFocus() hasn't fired
    // yet
    var pendingFocus = isc.EH._unconfirmedFocus;
    if (pendingFocus != null && pendingFocus != this) {
        this.logDebug("not restoring focus; focus about to move to:" + pendingFocus,
                      "nativeFocus");
        return;
    }
    this.logDebug("restoring focus from zIndex change", "nativeFocus");
    this._setFocusWithoutHandler(true);
},

//>	@method	canvas.focus()
// If this canvas can accept focus, give it keyboard focus. After this method, the canvas
// will appear focused and will receive keyboard events.
// @group	focus
// @visibility external
//<
focus : function (reason) {

    if (isc._traceMarkers) arguments.__this = this;
    
    this.setFocus(true, reason);
},


//>	@method	canvas.blur()
// If this canvas has keyboard focus, blur it. After this method, the canvas
// will no longer appear focused and will stop receiving keyboard events.
// @group	focus
// @visibility external
//<
blur : function (reason) {
    if (isc._traceMarkers) arguments.__this = this;
    this.setFocus(false, reason);
},

// focusAtEnd(): Helper method for synthetic tabIndex stuff - puts focus at the 'beginning' or
// 'end' of this widget.
// No effect unless this widget has some concept of focusable sub elements, for which it
// manages the tab order directly.
// Has meaningful implementation in DynamicForm / Toolbar by default
focusAtEnd : function (start) {
    return this.focus();
},

//>	@method	canvas._setFocusWithoutHandler()
//			Internal method to update whether this widget has focus or not, without triggering the
//          focusChanged handler
//		@group	focus
//      @visibility internal
//<
_setFocusWithoutHandler : function (state, reason) {
    this._suppressFocusChanged = true;
    this.setFocus(state, reason);

    
},


//>	@method	canvas._focusChanged() (I)
// Fired when this canvas is focused or blurred.  May cause redraw if redrawOnFocus is true.
//		@group	focus
//<

_focusChanged : function (hasFocus) {
    if (hasFocus == null) hasFocus = (this.ns.EH._focusCanvas == this);
    this.hasFocus = hasFocus;

    if (this._suppressFocusChanged) {
        delete this._suppressFocusChanged;
        return false;
    }

    // have a flag so the focusChanged so we know if we're firing in response to a focus/blur
    
    this._focusChanging = true;

    // if defined, call the focusChanged handler (stringMethod)
    if (this.focusChanged != null) {

        this.convertToMethod("focusChanged");
        this.focusChanged(hasFocus);
    }

	// if we're marked to redraw when focused, redraw!
	if (this.redrawOnFocus) this.markForRedraw("setFocus");
    this._focusChanging = false;
},

// if we have a focusOnHide target specified, focus in it, otherwise just blur
// Note: We're hiding ourselves and all our children here, so if we're hiding the current
// focus canvas we need to respect it's focusOnHide (if set)
_updateFocusForHide : function () {

    var fc = this.ns.EH.getFocusCanvas();

    if (this._isVisibilityAncestorOf(fc)) {

        if (isc.isA.Canvas(fc.focusOnHide) && fc.focusOnHide.isDrawn() &&
            fc.focusOnHide.isVisible()) {
                fc.focusOnHide.focus();
        }
        else {
            fc.blur();
            // In IE blur() is asynchronous - the blur handler won't fire until after the
            // end of the calling thread.
            // If the handle has disappeard from the DOM, it won't fire at all, which can leave
            // EH focusCanvas pointing to a hidden (or even destroyed) widget.
            // Explicitly call EH.blurFocusCanvas() now if we still have focus at this point
            // to avoid this. When the native blur handler fires EH._blurFocusCanvas will no-op
            // if appropriate.
            if (fc.hasFocus) isc.EH.blurFocusCanvas(fc);
        }
    }
},

//> @method canvas.containsFocus()
// Returns true if the keyboard focus is in this Canvas or any child of this Canvas.
// @return (Boolean) whether this Canvas contains the keyboard focus
// @group focus
// @visibility external
//<
containsFocus : function  () {
    var fc = this.ns.EH.getFocusCanvas();
    return this.contains(fc, true);
},




// Access Key
// --------------------------------------------------------------------------------------------
// Global keyboard shortcuts for switching focus to this Canvas

//>	@method	canvas.setAccessKey()	(A)
// Set the accessKey for this canvas.
// <P>
// The accessKey can be set to any alphanumeric character (symbols not supported)
// Having set an accessKey, the canvas will be given focus when the user hits
// Alt+[accessKey], or in Mozilla Firefox 2.0 and above, Shift+Alt+[accessKey].
//
//		@group	focus
//
//      @param accessKey
//              Character to use as an accessKey for this widget.  Case Insensitive.
// @visibility external
//<

setAccessKey : function (accessKey) {
    this.accessKey = accessKey;

    // Only set the accessKey on the handle if the widget is really focus-able
    if (this._canFocus() && !this.isDisabled()) {
        this._setHandleAccessKey(this.accessKey)
    }
},

// Internal method to actually set the accessKey on the widget handle (or focusProxy if
// appropriate).  No-op's if the handle is not drawn or if _useNativeTabIndex and
// _useFocusProxy are both false
_setHandleAccessKey : function (accessKey) {

    // accessKeyProxy stuff for Moz
    if (this._useAccessKeyProxy()) {
        if (accessKey == null) this._clearAccessKeyProxy();
        else {
            if (this._accessKeyProxy) this._accessKeyProxy.accessKey = accessKey;
            else this._makeAccessKeyProxy();
        }
        return;
    }

    if (this._useNativeTabIndex) {
        var handle = this.getHandle();
        if (handle != null) handle.accessKey = accessKey;
    }

    if (this._useFocusProxy && this._hasFocusProxy) {
        var handle = this._getFocusProxyHandle();

        if (handle != null) {
            
            if (isc.Browser.isMoz) {
                //>FocusProxy
                this._clearFocusProxy();
                this.makeFocusProxy();
                //<FocusProxy
            } else {
                handle.accessKey = accessKey;
            }
        }
    }

},

// Return the accessKey for the widget.
getAccessKey : function () {
    return this.accessKey;
},


// Tab Index
// --------------------------------------------------------------------------------------------
// Managing this Canvas position in the global tabbing order

// Return the Tab index for the widget.
// If not set, defaults to isc.Canvas.currentTabIndex, which increments from 1000 as more widgets
// are added to the tab order.
getTabIndex : function () {
    if (this.tabIndex == null) {
        this._autoAllocateTabIndex();
    }

    return this.tabIndex;
},

// Most widgets take up 1 tab index.
getTabIndexSpan : function () {
    return 1;
},

//>	@method	canvas.setTabIndex()	(A)
//  Assign an explicit tabIndex to this widget.
// @param tabIndex (number) New tabIndex for this widget. Must be less than
//                          +link{Canvas.TAB_INDEX_FLOOR} to avoid interfering with auto-assigned
//                          tab indices on the page.
// @group focus
// @see canvas.tabIndex
// @visibility external
//<
// See comments by _autoAllocateTabIndex() for ISC tab order tracking implementation details.
setTabIndex : function (index) {
    var floor = isc.Canvas.TAB_INDEX_FLOOR;
    // explicitly specified tabIndices must be below a certain floor so that they don't collide
    // with ISC auto-allocated tab indices.
    if (index >= floor) {
        var minIndex = floor - 1;
        this.logWarn("setTabIndex(): Passed index of " + index +
                     ". This method does not support setting a tab index greater than "
                     + minIndex +
                     ".  Setting tab index for this widget to " + minIndex +
                     this.getStackTrace());
        index = minIndex;
    }
    // Update the _previousTabWidget and _nextTabWidget flags
    this._removeFromAutoTabOrder();
    this._setTabIndex(index, false);
},

_setTabIndex : function (index, autoAllocated) {
    //!DONTCOMBINE

    // Note - this method doesn't manage _previousTabWidget and _nextTabWidget - those
    // properties should be updated by the calling methods.

    this._autoTabIndex = autoAllocated;

    this.tabIndex = index;
    if (this._canFocus() && !this.isDisabled()) {
        this._setHandleTabIndex(index);
    }
},

// _setHandleTabIndex
// Updates the tabIndex of a widget's handle -- will not effect the widgets 'tabIndex' property
// No-ops if it can't get to the widget's handle, or if _useNativeTabIndex is not true
_setHandleTabIndex : function (index) {
    if (this._useNativeTabIndex && this.isDrawn()) {
        var handle = this.getFocusHandle();
        handle.tabIndex = index;
        
        if (isc.Browser.isIE) isc.Canvas._forceNativeTabOrderUpdate();
    }

    //>FocusProxy
    if (this._useFocusProxy) {

        // We may not have a focus proxy yet - this could happen if we're calling this method
        // before delayed creation of a focus proxy, or in Safari going from tabIndex -1 to
        // a positive t.i.
        if (!this._hasFocusProxy) return this.makeFocusProxy();

        var handle = this._getFocusProxyHandle();
        // before manipulating the handle take focus from it
        // If necessary we'll restore focus after changing the tabIndex
        
        
        var hasFocus = (this.hasFocus && !this._focusChanging);
        if (hasFocus && handle) handle.blur();

        // In safari, it is impossible to make a native focus proxy and exclude it from the
        // page's tab order, so we just clear it to remove the widget from the tab order of the
        // page.
        if (isc.Browser.isSafari && index < 0) return this._clearFocusProxy();


        if (handle != null) {
            handle.tabIndex = index;
            
            if (isc.Browser.isMoz) {
                handle.style.MozUserFocus = (index < 0 ? "ignore" : "normal");
            }
            
            if (hasFocus) handle.focus();
        }
    }
    //<FocusProxy
},


// If no tabIndex is specified for the widget (and it is focus-able), automatically
// assign one.
// In IE, tabIndex can validly be any integer from -32767 to 32767
// Negative values are ommitted from the tab order.
//
// We manage auto-assigning tab indexes for widgets in the following way:
// A widget is auto-assigned a tabIndex at draw time.  We start the ISC tab index count at 1000,
// and increment this value by 50 for each new widget.
// Each widget can be assumed to take up a 'span' of tab index slots - by default one slot, but
// for a dynamic form one slot per focusable item. The method 'getTabIndexSpan()' should return
// this value.

//
// If a developer wants to explicitly specify tab-index for some widgets, they can set the
// values to anything below 1000 to avoid interfering with the auto-assigned tab indexes.
// If the widget is a dynamic form, the developer should be aware that the form items will take
// up slots in the page's tab order, so they may have to leave gaps between DynamicForm widgets.
// We may want to make 'getTabIndexSpan()' external for this reason.
//
// For widgets with auto-assigned tab indexes, we allow the widgets to keep track of where they
// are in the tab order by setting pointers to the next and previous widget in the tab order
// (_nextTabWidget and _previousTabWidget).
// We also support runtime reordering of auto-assigned tabindex widgets via the internal
// _setTabBefore() and _setTabAfter() methods. This is used to ensure that Layout members
// appear in the appropriate order on the page





_autoAllocateTabIndex : function () {
    var Canvas = isc.Canvas;
    if (Canvas._currentTabIndex == null) {
        Canvas._currentTabIndex = Canvas.TAB_INDEX_FLOOR;
    }

    var currentTabWidget = isc.EH._lastTabWidget;
    if (currentTabWidget) Canvas._currentTabIndex += currentTabWidget.getTabIndexSpan()

    // Always leave a significant gap between widgets when first setting them up - makes
    // it easier to slot other widgets in between them in the page's tab order.
    Canvas._currentTabIndex += Canvas.TAB_INDEX_GAP

    // If we hit the native browser tabindex ceiling warn about it
    
    if (Canvas._currentTabIndex > isc.Canvas.TAB_INDEX_CEILING &&
        !isc.Canvas._tabIndexCeilingWarned)
    {
        isc.Canvas.logWarn("Auto allocation of tab-indices has reached native browser ceiling " +
                           "- tab-order cannot be guaranteed for widgets on this page.");
        isc.Canvas._tabIndexCeilingWarned = true;
    }
    
    this._setTabIndex(Canvas._currentTabIndex, true);

    // update the flags to point to the previous widget in the auto-allocated tab order
    if (currentTabWidget) {
        currentTabWidget._setNextTabWidget(this);
        this._previousTabWidget = currentTabWidget;
    } else {
        isc.EH._firstTabWidget = this;
    }
    isc.EH._lastTabWidget = this;
},

// limitation: if you try to move before or after a widget with an explicitly
// specified tab index we can't manage the order properly.
// Also updates any auto-assigned children's tab indexes to keep them in order within the page
// level tab order.
// If this method is called on a non-focusable widget, it will only update any focusable
// children.

_setTabBefore : function (otherWidget) {
    // No need to take action if attempting to move before this, or if we're already before the
    // other widget
    if (this == otherWidget || this._getNextTabWidget() == otherWidget) return;

    // note: getTabIndex() will set the _autoTabIndex flag if no tab index was explicitly
    // specified
    var newTabIndex = otherWidget.getTabIndex();
    if (!otherWidget._autoTabIndex) {
        //>DEBUG
        this.logWarn("_setTabBefore() attempting to set tab index adjacent to widget "
                    + otherWidget + " with explicitly specified tabIndex [" + otherWidget.tabIndex
                    + "]. This method can only manipulate widgets with auto-assigned tab indexes.");
        //<DEBUG
        return;
    }

    var previousWidget = otherWidget._previousTabWidget;

    

    // Remove this widget from it's current position in the tab order -
    // If we have a previous / next widget in the tab order, update it's flag pointing back to us
    // to point to the appropriate widget after / before us in the tab order
    var prev = this._getPreviousTabWidget(),
        next = this._getNextTabWidget();
    if (isc.EH._lastTabWidget == this) isc.EH._lastTabWidget = prev;
    if (isc.EH._firstTabWidget == this) isc.EH._firstTabWidget = next;
    if (prev != null)
        prev._setNextTabWidget(next);
    if (next != null)
        next._setPreviousTabWidget(prev);

    this._setPreviousTabWidget(null);
    this._setNextTabWidget(null);

    this._slotTabBetween(otherWidget._getPreviousTabWidget(), otherWidget);

    // If we have children, ensure they show up after us in the tab order.
    
    this._slotChildrenIntoTabOrder();
},

_setTabAfter : function (otherWidget) {

    // No need to take action if attempting to move before this, or if we're already before the
    // other widget
    if (this == otherWidget || this._previousTabWidget == otherWidget) return;


    // note: getTabIndex() will set the _autoTabIndex flag if no tab index was explicitly specified
    otherWidget.getTabIndex();

    if (!otherWidget._autoTabIndex) {
        //>DEBUG
        this.logWarn("_setTabAfter() attempting to set tab index adjacent to widget "
                    + otherWidget + " with explicitly specified tabIndex [" + otherWidget.tabIndex
                    + "]. This method can only manipulate widgets with auto-assigned tab indexes.");
        //<DEBUG
        return;
    }

    var previousWidget = otherWidget,
        oldPrev = this._getPreviousTabWidget(),
        oldNext = this._getNextTabWidget();

    if (isc.EH._lastTabWidget == this) isc.EH._lastTabWidget = oldPrev;
    if (isc.EH._firstTabWidget == this) isc.EH._firstTabWidget = oldNext;

    // If we have a previous / next widget in the tab order, update it's flag pointing back to us
    // to point to the appropriate widget after / before us in the tab order
    if (oldPrev != null)
        oldPrev._setNextTabWidget(oldNext);
    if (oldNext != null)
        oldNext._setPreviousTabWidget(oldPrev);

    // (and clear these flags on us!)
    this._setPreviousTabWidget(null);
    this._setNextTabWidget(null);

    // Now just slot in between the other widget and the one that follows it
    this._slotTabBetween(otherWidget, otherWidget._getNextTabWidget());

    // If we have children, ensure they show up after us in the tab order.
    this._slotChildrenIntoTabOrder();
},

// Insert this widget into the auto-assigned tab order between 2 other widgets
_slotTabBetween : function (previous, next) {

    // This will automatically set up EH._lastTabWidget
    if (next == null) return this._autoAllocateTabIndex();

    // We can't easily slot in front of the first widget in the tab-order - in this case we
    // need to slot into the first widgets slot and shift the first widget forward to avoid
    // assigning a tabIndex < the isc-managed tabIndex floor.
    if (previous == null) {
        var nextNext = next._getNextTabWidget();
        next._removeFromAutoTabOrder();
        this._setNextTabWidget(nextNext);
        this._setPreviousTabWidget(null);
        this._setTabIndex(next.tabIndex, true);
        isc.EH._firstTabWidget = this;

        next._slotTabBetween(this, nextNext);
        return;
    }

    // At this point we have 2 valid tabindex-adjacent widgets to slot between

    // update the _nextTabWidget / _previousTabWidget flags
    this._setNextTabWidget(next);
    next._setPreviousTabWidget(this);
    this._setPreviousTabWidget(previous);
    previous._setNextTabWidget(this);

    var previousTabIndex = previous.tabIndex + previous.getTabIndexSpan(),
        nextTabIndex = next.tabIndex,
        // split the difference between the previous widgets tabIndex (plus its required slots)
        // and the next tabIndex.
        newTabIndex = previousTabIndex + Math.floor((nextTabIndex - previousTabIndex)/2),
        span = this.getTabIndexSpan();
    // if our required tabIndexSpan overlaps the next tabIndex, we need to shift the next widget
    // forwards
    
    if ((newTabIndex + span) > nextTabIndex) {
        next._shiftTabIndexForward((newTabIndex + span) - nextTabIndex);
        // This calculation is now guaranteed to give us enough space
        //this.logWarn("Our span:" + span +": not enough tabIndex gap between:"
        //                + previous + ", " + previous.tabIndex
        //                + " and next:"+ next + ":" + nextTabIndex +
        //                  " - resolved by shifting next to:"+ next.tabIndex);

    }
    if (this.logIsDebugEnabled("tabIndex")) {
        this.logDebug("Putting " + this.getID() + " in tab order between: "+ previous.getID() +
                     ":"+ previous.tabIndex + ", and :"+ next.getID() + ":" + next.tabIndex +
                     ". Resulting tabIndex:"+ newTabIndex, "tabIndex");
    }

    this._setTabIndex(newTabIndex, true);
},


// Shunt our tabIndex forward by the number of slots passed in.
// If we don't have room without colliding with the tabIndex of this._nextTabWidget,
// shift that widget forward as well (recursively)
_shiftTabIndexForward : function (minimumRequired) {

    

    var next = this._getNextTabWidget();
    if (next == null) {
        this._setTabIndex(this.tabIndex + minimumRequired + isc.Canvas.TAB_INDEX_GAP, true);
        return;
    }

    // Shunt ourselves right up against the guy after us by default.
    // If that doesn't give us enough room we'll have to shift him forward too.
    var nextTI = next.getTabIndex(),
        newTI = nextTI - this.getTabIndexSpan();
    if (this.tabIndex + minimumRequired < newTI) this._setTabIndex(newTI, true);
    else {
        // Shift the next widget forward by the space we need to shift forwards by less the
        // space we actually can shift forwards by
        next._shiftTabIndexForward(minimumRequired - (newTI - this.tabIndex));
        // And now when we shunt up against it we know there will be enough space.
        this._setTabIndex(next.tabIndex - this.getTabIndexSpan(), true);
    }
},


_getNextTabWidget : function (backwards) {
    if (!backwards) return this._nextTabWidget;
    else return this._previousTabWidget;
},

_getPreviousTabWidget : function () {
    return this._getNextTabWidget(true);
},

_setNextTabWidget : function (widget, backwards) {

    if (!backwards) this._nextTabWidget = widget;
    else this._previousTabWidget = widget;
},

_setPreviousTabWidget : function (widget) {
    return this._setNextTabWidget(widget, true);
},

//>	@method	canvas.focusInNextTabElement()
// Shifts focus to the next focusable element after this one.  This is the programmatic 
// equivalent of the user pressing the Tab key.  A "focusable element" is a +link{class:Canvas}
// or +link{class:FormItem} that is +link{canvas.canFocus,focusable}, and is not  
// +link{formItem.disabled,disabled} or +link{canvas.showClickMask,masked}.  Note that the 
// movement of focus to a <code>FormItem</code> is managed by the containing 
// +link{class:DynamicForm}; calling this method on a <code>DynamicForm</code> will move the 
// focus to the next focusable <code>FormItem</code> in the form if there is one, and onto the
// next focusable <code>Canvas</code> if not.
// <p>
// <b>NOTE: </b>This method only considers SmartClient elements.  Native elements that you 
// create directly - for example, with +link{HTMLFlow.setContents()} or by creating a custom
// widget with +link{Canvas.getInnerHTML()} that has focusable elements that are not child
// Canvases - will not be noticed by this method.
//
// @group focus
// @see canvas.tabIndex
// @see canvas.focusInPreviousTabElement
// @visibility external
//<
focusInNextTabElement : function() {
    if (this._focusInNextTabElement) {
        this._focusInNextTabElement(true, this._getTopHardMask());
    }
},


//>	@method	canvas.focusInPreviousTabElement()
// Shifts focus to the previous focusable element before this one.  This is the programmatic 
// equivalent of the user pressing Shift-Tab.  A "focusable element" is a +link{class:Canvas}
// or +link{class:FormItem} that is +link{canvas.canFocus,focusable}, and is not  
// +link{formItem.disabled,disabled} or +link{canvas.showClickMask,masked}.  Note that the 
// movement of focus to a <code>FormItem</code> is managed by the containing 
// +link{class:DynamicForm}; calling this method on a <code>DynamicForm</code> will move the 
// focus to the previous focusable <code>FormItem</code> in the form if there is one, and onto
// the previous focusable <code>Canvas</code> if not.
// <p>
// <p>
// <b>NOTE: </b>This method only considers SmartClient elements.  Native elements that you 
// create directly - for example, with +link{HTMLFlow.setContents()} or by creating a custom
// widget with +link{Canvas.getInnerHTML()} that has focusable elements that are not child
// Canvases - will not be noticed by this method.
//
// @group focus
// @see canvas.tabIndex
// @see canvas.focusInNextTabElement
// @visibility external
//<
focusInPreviousTabElement : function() {
    if (this._focusInNextTabElement) {
        this._focusInNextTabElement(false, this._getTopHardMask());
    }
},

_getTopHardMask : function () {
    return isc.EH.getTopHardMask();
},

_focusInNextTabElement : function (forward, mask) {

    
    if (isc.CanvasItem && this.isDrawn() && this.isVisible()) {
        var canvasItemParent = null, canvas = this;
        do {
            if (canvas.canvasItem != null) {
                canvasItemParent = canvas.canvasItem;
            } else {
                canvas = canvas.parentElement;
            }
        } while (canvas && (canvasItemParent == null))
        
        if (canvasItemParent && canvasItemParent.form) {
            
            this.logInfo("_focusInNextTabElement() called on a descendent of a CanvasItem " +
                canvasItemParent + ". Delegating focus manipulation to parent form " + 
                canvasItemParent.form, "syntheticTabIndex");
                
            return canvasItemParent.form._focusInNextTabElement(forward, mask);
        }
        
    }


	// If we are a canvasItem in some DynamicForm, have the form shift focus to the next
	// item, rather than finding the next tab-widget as we normally would.
	
	if (isc.CanvasItem && this.canvasItem != null && this.canvasItem.form != null) {
		this.canvasItem.form._focusInNextTabElement(forward,mask);
		return;
	}

    var nextWidget = this;
    do {
        nextWidget = (forward ? nextWidget._getNextTabWidget() :
                                nextWidget._getPreviousTabWidget());

    } while(nextWidget &&
            (isc.EH.targetIsMasked(nextWidget, mask) || nextWidget.isDisabled() ||
             !nextWidget.isDrawn() || !nextWidget.isVisible()  || !nextWidget._canFocus()) &&
            // don't shove focus into a CanvasItem's canvas -- here. This would be handled via
            // the DynamicForm override to focusInItem() on the appropraite item (which would
            // stick focus into the embedded Canvas if appropriate).
            (!isc.CanvasItem || nextWidget.canvasItem == null || nextWidget.canvasItem.form == null))

    if (nextWidget) {
        //>DEBUG
        this.logInfo("focusInNextTabElement() shifting focus to:"+ nextWidget, "syntheticTabIndex");
        //<DEBUG

        
        nextWidget.focusAtEnd(forward)
    } else if (forward) {
        //>DEBUG
        this.logInfo("focusInNextTabElement() shifting focus to first widget", "syntheticTabIndex");
        //<DEBUG
        if (isc.EH._firstTabWidget == null ||
            // If we're the first widget in the synthetic tab order and we're non focusable
            // telling EH to drop focus into the first guy will cause EH to call this
            // method again, leading to a potential infinite loop.
            (isc.EH._firstTabWidget == this &&
             (this.isDisabled() || !this.isDrawn() ||
             !this.isVisible()  || !this._canFocus() || this.isMasked(mask))))
         {
             return;
         }
        isc.EH._focusInFirstWidget(mask);
    } else {
        //>DEBUG
        this.logInfo("focusInNextTabElement() shifting focus to last widget", "syntheticTabIndex");
        //<DEBUG
        if (isc.EH._lastTabWidget == null ||
            (isc.EH._lastTabWidget == this &&
             (this.isDisabled() || !this.isDrawn() ||
             !this.isVisible()  || !this._canFocus() || this.isMasked(mask))))
        {
            return;
        }
        isc.EH._focusInLastWidget(mask);
    }
},

// Helper - if we've got an autoAllocated tabIndex, ensure our children show up after this
// widget in the page's tab order.

_slotChildrenIntoTabOrder : function () {
    
    var children = isc.isA.Layout(this) ? this.members : this.children;
    if (!children || children.length == 0) return;

    
    var afterChild = this._getNextTabWidget();
    for (var i = children.length -1; i >= 0; i--) {
        // support sparse arrays
        if (children[i] == null || (children[i].tabIndex != null && !children[i]._autoTabIndex)) continue;
        // Catch the case where we are the last auto-allocated tab widget
        if (afterChild == null) children[i]._setTabAfter(this);
        else children[i]._setTabBefore(afterChild);
        afterChild = children[i];
    }
},

// Helper to get the last descendant of a widget with auto-assigned tab index so
// setTabAfter() can reliably put the widget after the prev-widget's descendants.
// Currently only used in Layouts.

_getLastAutoIndexDescendant : function (lookAtAssignedPosition) {

    var children = this.children;
    
    if (isc.Layout && isc.isA.Layout(this)) children = this.members;
    if (children != null) {
        for (var i = children.length -1; i >= 0; i--) {
            // skip nulls in case we're called pre-draw on a Layout
            if (children[i] == null || (children[i].tabIndex != null && !children[i]._autoTabIndex))
                continue;

            var descendant = children[i]._getLastAutoIndexDescendant();
            if (descendant != null) return descendant;
        }
    }

    // If we're still going, we didn't find any descendants with auto-assigned TI.
    // return ourselves if appropriate
    if (this.tabIndex == null || this._autoTabIndex) return this;
    return null;
},


// Helper function to update the pointers to the next and previous widget in the page's tab order
// when we stop managing a widget's tab position.
// This can happen if the widget's tab position is specified explicitly via 'setTabIndex()', or if
// the widget becomes un-focusable.
// This function doesn't remove the widget from the tab order of the page - it doesn't set the
// native tab index to -1 or null.
// It just
_removeFromAutoTabOrder : function () {
    // if we're not managing the tab index, nothing to do
    if (!this._autoTabIndex || !this.tabIndex) return;

    var prevWidget = this._getPreviousTabWidget(),
        nextWidget = this._getNextTabWidget();

    // if this method has been run before, we can just return
    if (prevWidget == null && nextWidget == null && isc.EH._lastTabWidget != this &&
        isc.EH._firstTabWidget != this) return;


    if (prevWidget) {
        prevWidget._setNextTabWidget(nextWidget);
    } else {
        isc.EH._firstTabWidget = nextWidget;
    }

    if (nextWidget) {
        nextWidget._setPreviousTabWidget(prevWidget);
    } else {
        
        isc.EH._lastTabWidget = prevWidget;
    }

    this._setPreviousTabWidget(null);
    this._setNextTabWidget(null);
},

// zIndex (stacking order)
// --------------------------------------------------------------------------------------------
// See also class method Canvas.getNextZIndex();

//>	@method	canvas.getZIndex()	(A)
//	Get the z-Index of this canvas.<br><br>
//
//		@group	zIndex
//
//      @param resolveToNumber(boolean)
//              If passed <code>true</code>, for undrawn widgets, resolve "auto" to the next available zIndex.
//
//		@return	(number)
//<
getZIndex : function (resolveToNumber) {

    
    if (!this.isDrawn() || isc.Browser.isSafari) {
        // if passed the 'resolveToNumber' parameter, update the zIndex to be a valid number.
        if (resolveToNumber && this.zIndex == isc.Canvas.AUTO) {
            this.setZIndex(isc.Canvas.getNextZIndex());
        }
        return this.zIndex;
    }

    return parseInt(this.getStyleHandle().zIndex);
},

//>	@method	canvas.setZIndex()	(A)
//			set the z-Index of the canvas.
//		@group	zIndex
//		@param	newIndex		(number)	new zIndex to set to
//<

setZIndex :	function (newIndex) {
    var oldZIndex = this.zIndex;
    if (oldZIndex == newIndex) return;

    var hasNativeFocusInIE = false;

    // NOTE: In IE there is a native bug whereby if the handle of a canvas has native
    // focus, and we attempt to set the z-index, the widget will not have its z-index updated.
    // Fix this by natively blurring the canvas, and re-focussing at the end of the message
    // (without firing any focus/blur handlers)
    if (isc.Browser.isIE && this.hasFocus && this._useNativeTabIndex)
    {
        hasNativeFocusInIE = true;
        this.logDebug("blurring due to zIndex change", "nativeFocus");
        this._setFocusWithoutHandler(false);
    }

    // adjust zIndices of the backmask and other special peers.  Note - we do this first if
    // we're moving the widget back in the Z-order, and last if we're moving the widget forward
    // in the z-order, so the peers never pop in front
    
    if (newIndex < oldZIndex) this._adjustSpecialPeers(newIndex);

	this.zIndex = newIndex;
	if (this.isDrawn()) {

        // if using two DIVs, set zIndex for both DIVs that we draw
        if (this._drewClipDiv) this.getHandle().style.zIndex = newIndex

        this.getStyleHandle().zIndex = newIndex;
    }

    if (newIndex > oldZIndex) this._adjustSpecialPeers(newIndex);

    // Keep custom scrollbars above us.
    if (this.hscrollbar) this.hscrollbar.moveAbove(this);
    if (this.vscrollbar) this.vscrollbar.moveAbove(this);

    //>CornerClips keep corner clips above us
    if (this.clipCorners) {
        var clips = this._cornerClips;
		if (clips.TL) clips.TL.moveAbove(this);
		if (clips.TR) clips.TR.moveAbove(this);
		if (clips.BL) clips.BL.moveAbove(this);
		if (clips.BR) clips.BR.moveAbove(this);
    }
    //<CornerClips

    if (hasNativeFocusInIE) {
        // refocus on a timeout if we've blurred.
        
        this.delayCall("_restoreFocus", [], 0);
    }

    this.zIndexChanged(oldZIndex, newIndex);
},

_adjustSpecialPeers : function (newIndex) {
    if (this._edgesAsPeer()) this._edgedCanvas.setZIndex(newIndex-1);
    if (this._backMask) this._backMask.setZIndex(newIndex-2);
    if (this._shadow) this._shadow.setZIndex(newIndex-3);
    if (this.modalMask) this.modalMask.setZIndex(newIndex-4);
},

// zIndexChanged - notification fired if our zIndex has changed.
// calls 'parentZIndexChanged()' on any descendants by default.
zIndexChanged : function (oldZIndex, newZIndex) {
    if (this.children) this.children.map("parentZIndexChanged");
},

// parentZIndexChanged - notification fired when an ancestor's zIndex has changed.
// recursively calls this same method on any descendants so the whole descendant-chain is
// notified of the ZIndex change.
parentZIndexChanged : function () {
    if (this.children) this.children.map("parentZIndexChanged");
},


//>	@method	canvas.bringToFront()   ([])
// Puts this widget at the top of the stacking order, so it appears in front of all other
// widgets in the same parent.
//      @visibility external
//		@group	zIndex
//      @example    layer
//<

bringToFront : function (skipSoftUnmask) {
    if (isc._traceMarkers) arguments.__this = this;

    isc.Canvas._BIG_Z_INDEX += 18;
	this.setZIndex(isc.Canvas._BIG_Z_INDEX);

    
    if (skipSoftUnmask && !this._isHardMasked()) return;

    isc._unmaskOnBringToFront = true;
    this.unmask();
    isc._unmaskOnBringToFront = false;
},


//>	@method	canvas.sendToBack() ([])
// Puts this widget at the bottom of the stacking order, so it appears behind all other
// widgets in the same parent.
//      @visibility external
//		@group	zIndex
//      @example    layer
//<

sendToBack : function () {
	isc.Canvas._SMALL_Z_INDEX -= 18;
	this.setZIndex(isc.Canvas._SMALL_Z_INDEX);
},


//>	@method	canvas.moveAbove()  ([])
// Puts this widget just above the specified widget in the stacking order, so it appears in
// front of the specified widget if both widgets have the same parent.
//      @visibility external
//		@group	zIndex
//		@param	canvas		(Canvas or subclass)	canvas to move above
//      @example    layer
//<
moveAbove : function (canvas) {
    // if the other Canvas has "auto" because it hasn't drawn yet, assign it a zIndex now
    // Note - this method will always set the zIndex so that this widget is adjacent to the
    // other widget (may lower the zIndex of this widget if it is already well above the other
    // widget).
    // Therefore we can't no-op if this widget is already above the other widget without a
    // functional change.
    var z = canvas.getZIndex(true);
    this.setZIndex(z + 6);
},


//>	@method	canvas.moveBelow()
// Puts this widget just below the specified widget in the stacking order, so it appears
// behind the specified widget if both widgets have the same parent.
//      @visibility external
//		@group	zIndex
//		@param	canvas		(Canvas or subclass)	canvas to move below
//      @example    layer
//<
moveBelow : function (canvas) {
    // Note - this method will always set the zIndex so that this widget is adjascent to the
    // other widget (may raise the zIndex of this widget if it is already well below the other
    // widget).
    // Therefore we can't no-op if this widget is already below the other widget without a
    // functional change.
    // if the other Canvas has "auto" because it hasn't drawn yet, assign it a zIndex now
    var z = canvas.getZIndex(true);
    this.setZIndex(z - 6);
},


// Setting / Getting HTML Content
// --------------------------------------------------------------------------------------------

//>	@method	canvas.getContents()
//      Returns the contents of a Canvas. The contents are an HTML string.
// @return (HTML) contents of this Canvas
// @visibility external
//<
getContents : function () {
	var contents = (isc.isA.Function(this.contents) ? this.contents() : this.contents);
	return this.dynamicContents ?
            contents.evalDynamicString(this, this.dynamicContentsVars) :
            contents;
},


//>	@method	canvas.setContents()
// Changes the contents of a widget to newContents, an HTML string.
// <P>
// When +link{canvas.dynamicContents,dynamicContents} is set, <code>setContents()</code> can
// also be called with no arguments to cause contents to be re-evaluated.
//
//  @param	[newContents]	(string)    an HTML string to be set as the contents of this widget
//  @visibility external
//  @example setContents
//<
setContents : function (newContents) {
    if (newContents != null) this.contents = newContents;
	this.markForRedraw("setContents");
},

// Loading HTML Content
// ---------------------------------------------------------------------------------------

containsIFrame : function () {
    return this.contentsURL != null && this.contentsType == "page";
},



//>	@method	canvas.getContentsURL()    ()
//      Returns the contentsURL of a widget.
//<
getContentsURL : function () {
	return this.contentsURL;
},

//>	@method	canvas.setContentsURL()
//	Change the contentsURL of the Canvas
//
//		@param	newURL	(string)
//<
setContentsURL : function (url, params) {
    // store new URL
	this.contentsURL = url;

    // support special prefixes, eg [APPFILES]
    url = isc.Page.getURL(url);
    // support params (NOTE: doc'd under HTMLFlow)
    var allParams = isc.addProperties({}, this.contentsURLParams, params),
    url = isc.rpc.addParamsToURL(url, allParams);

    if (!this.isDrawn()) return;

    if (this.containsIFrame()) {
        // Modify the src property on the IFRAME to move to the new URL.
        var urlHandle = this._getURLHandle();
        
        if (!urlHandle || !url) this.markForRedraw("setContentsURL");
        else urlHandle.src = url;
    }
},

// Miscellaneous styling setters
// --------------------------------------------------------------------------------------------

//> @method canvas.setBackgroundColor()
// Sets the background color of this component to <code>newColor</code>.
// @param newColor (CSSColor) new background color, or <code>null</code> to remove the current
// background color.
// @group appearance
// @visibility external
//<
setBackgroundColor : function (newColor) {
    this.backgroundColor = newColor;
    var styleHandle = this.getStyleHandle();
    if (styleHandle != null) {
        
        styleHandle.backgroundColor = (newColor == null ? "" : newColor);
    }
},


setTextColor : function (newColor) {
    this.textColor = newColor;
    var styleHandle = this.getStyleHandle();
    if (styleHandle) {
		return styleHandle.color = newColor;
	}
},

//>	@method	canvas.setBackgroundImage()
//			Sets the background to an image file given by newImage. This URL should be given as a
//          string relative to the image directory for the page (./images by default).
//		@group	appearance
//		@param	newImage	(string)	new URL (local to Page image directory) for background image
//      @visibility external
//<
setBackgroundImage : function (newImage) {
    this.backgroundImage = newImage;
    var styleHandle = this.getStyleHandle();
    if (styleHandle) {
        if (newImage == null) {
    		styleHandle.backgroundImage = "";
        } else {
    		styleHandle.backgroundImage = 'url(' + this.getImgURL(this.backgroundImage) + ')';
        }
	}
},


//>	@method	canvas.setBorder()
// Set the CSS border of this component, as a CSS string including border-width,
// border-style, and/or color (eg "2px solid blue").
// <P>
// This property applies the same border to all four sides of this component.  Different
// per-side borders can be set in a CSS style and applied via +link{styleName}.
//
// @group appearance
// @param newBorder (string) new border to set to (eg: "2px solid black")
// @visibility external
//<
setBorder : function (newBorder) {
    this._cachedBorderSize = null;

    if (newBorder != null && !isc.isA.String(newBorder)) {
        newBorder = this._convertBorderToString(newBorder);
    }

    if (newBorder == null) newBorder = isc.emptyString;

    // Avoid a mysterious JS error in IE6 if someone passes in a string like
    // "2px solid gold;" rather than "2px solid gold"
    if (isc.endsWith(newBorder, isc.semi)) newBorder = newBorder.slice(0,newBorder.length-1);

    this.border = newBorder;

    var styleHandle = this.getStyleHandle();
    // if we're undrawn, no need to continue
    if (!styleHandle) return;
    if (styleHandle.border != newBorder) {
        styleHandle.border = newBorder;
    }
    
    this.adjustOverflow("setBorder");
    
    this.innerSizeChanged("Border thickness changed");
},

// convert a non-string border into something usable, or drop it
_convertBorderToString : function (border) {
    var specifiedBorder = border;
    if (isc.isA.Number(border)) {
        border += "px solid";
    } else {
        border = null;
         //>DEBUG
         this.logWarn("this.border defined as " + specifiedBorder +
                    ". This property should have a string value - dropping this attribute.");
         //<DEBUG
    }

    return border;
},

// convert a non-string border radius into something usable, or drop it
_convertBorderRadiusToString : function (borderRadius) {
    return (isc.isA.Number(borderRadius) ? borderRadius + "px" : null);
},

//>	@method	canvas.getBorder()
// Get the border for this canvas
//		@group	appearance
//		@return (string)    css border string (eg: "2px solid black")
//<
getBorder : function () {
    return this.border;
},

//>	@method	canvas.setOpacity() ([])
// Sets the opacity for the widget to the newOpacity value. This newOpacity
// value must be within the range of 0 (transparent) to 100 (opaque).
// Null means don't specify opacity directly.
// Note that heavy use of opacity may have a performance impact on some older
// browsers.
// <P>
// In older versions of Internet Explorer (Pre IE9 / HTML5), opacity is achieved
// through proprietary filters. If
// +link{canvas.neverUseFilters,filters have been disabled} within this application
// developers must set +link{canvas.useOpacityFilter} to true for specific components
// on which opacity support is required.
// <P>
// Also note that opacity is incompatible
// with +link{canvas.useBackMask,backMasks}.
//		@group	cues
//		@param	newOpacity	(number)	new opacity level
//      @visibility external
//      @example translucency
//<
//>Animation
// @param [animating] (boolean) passed if this setOpacity is part of an animated set opacity
//<Animation
setOpacity : function (newOpacity, animating, forceFilter) {
    //this.logWarn("setOpacity: " + newOpacity + ", animating: " + animating +
    //             this.getStackTrace());

    //>Animation
    if (!animating && this.fadeAnimation) this.finishAnimation("fade");
    //<Animation
    var oldOpacity = this.opacity;
    this.opacity = newOpacity;

    // ensure we null out the opacity setting when we go back to 100 (opaque), except for Moz
    // (see below).
    // In IE at least, this avoids issues where filters interact with each other unexpectedly,
    // specifically an Alpha filter on some parent will cause burn through issues with Gradient
    // or AlphaImageLoader filters that appear within table cells nested somewhere underneath
    if (this.opacity == 100 && !forceFilter && !(this.smoothFade && isc.Browser.isMoz)) this.opacity = null;

    if (this.isDrawn()) {
        if (isc.Browser.isMoz) {
            
            var opacity = (this.opacity != null) ? this.opacity / 100 : "";
            
            if (this.smoothFade && (opacity == 1 || this.opacity == null)) opacity = 0.9999;
            if (this._useMozOpacity) this.getStyleHandle().MozOpacity = opacity;
            else this.getStyleHandle().opacity = opacity;

        } else if (isc.Browser.isIE && !isc.Browser.isIE9) {
            
            if (!isc.Canvas.neverUseFilters || this.useOpacityFilter) {
                // Using proprietary Microsoft filters to achieve opacity
                this.getStyleHandle().filter = (this.opacity == null ? "" :
                        "progid:DXImageTransform.Microsoft.Alpha(opacity="+this.opacity+")");
            }

            

        // Safari, Opera, other: CSS3 opacity
        } else {
            var opacity = (this.opacity != null) ? this.opacity / 100 : "";
            this.getStyleHandle().opacity = opacity;
        }
	}

    this._setPeersOpacity(newOpacity, animating, forceFilter || newOpacity != null);

    if (isc.Browser.isIE && this.fixIEOpacity && this.children) {
        
        for (var i = 0; i < this.children.length; i++) {
            var child = this.children[i];
            if (child.opacity == null && (forceFilter || newOpacity != null)) {
                //this.logWarn("setting child: " + child + " to 100");
                child.setOpacity(100, animating, true);
            } else if (child.opacity == 100) {
                //this.logWarn("setting child: " + child + " to null");
                child.setOpacity(null);
            }
        }
    }

    this.opacityChanged(newOpacity, animating);
},

//>	@method	canvas.opacityChanged() ([])
//  Observable method called whenever a Canvas changes opacity.
// @param	newOpacity	(number)	new opacity level
//<
//>Animation
// @param [animating] (boolean) passed if this setOpacity is part of an animated set opacity
//<Animation
opacityChanged : function (newOpacity, animating) {},

// When opacity changes, update peers with new opacity
_setPeersOpacity : function (newOpacity, animating, forceFilter) {
    if (!this.peers) return;
    for (var i = 0; i < this.peers.length; i++) {

        if (this.peers[i]._setOpacityWithMaster) {
            if (this.useOpacityFilter != null) {
                this.peers[i].useOpacityFilter = this.useOpacityFilter;
            }
            this.peers[i].setOpacity(newOpacity, animating, forceFilter);
        } else if (this.peers[i] == this.edgedCanvas && this.edgeOpacity) {
            if (this.useOpacityFilter != null) {
                this.peers[i].useOpacityFilter = this.useOpacityFilter;
            }
            // If edgeOpacity is set, convert it to a percentage of the parents opacity
            var compOpacity = Math.round(this.opacity * (this.edgeOpacity * .01));
            this.peers[i].setOpacity(compOpacity, animating, forceFilter);
        }
    }
},



//>	@method	canvas.setPrompt()
// Set the prompt for this Canvas. If <code>this.showHover</code> is true this will be displayed
// in a hover for this canvas.
//		@group	cues
//<
setPrompt : function (prompt) {
    this.prompt = prompt;
    this.updateHover();
},

// Cursor handling
// --------------------------------------------------------------------------------------------

//>	@method	canvas.setCursor()  ([])
//			Sets the cursor for this widget to cursor. See the cursor property
//          for possible values.
//      @visibility external
//		@group	cues
//		@param	newCursor	(Cursor)	new cursor
//<
setCursor : function (newCursor) {
    if (newCursor && newCursor != this.cursor) {
        this.cursor = newCursor;
        // Call updateCursor to show the new cursor if appropriate
        this._updateCursor();
    }
},

//>	@method	canvas._applyCursor()     (I)
//		@group	cues
//			Internal method - actually updates the HTML to show a new cursor
//		@param	newCursor	(Cursor)	new cursor
//<
_applyCursor : function (newCursor) {
    if (this._styleCursor == newCursor) return;
    if (this.isDrawn()) {
        
        if (
            (isc.Browser.isMoz || (isc.Browser.isStrict && isc.Browser.isSafari))
            && newCursor == "hand") newCursor = isc.Canvas.HAND;

        this._styleCursor = newCursor;

        this.getStyleHandle().cursor = newCursor;
        // In double-div browsers, set the cursor of the content div.
        
        if (this._drewClipDiv) this.getHandle().style.cursor = newCursor;

        // If we are having events proxied to us, update the proxiers' cursors too, since
        // the proxiers' should be treated essentially like an extension of this widget's handle
        if (this._proxiers) {
            for (var i = 0; i < this._proxiers.length; i++) {
                this._proxiers[i]._applyCursor(newCursor);
            }
        }

        if (this.ns.EH._mouseMask && (this == this.ns.EH.getTarget())) {
            this.ns.EH._mouseMask.setCursor(newCursor);
        }

        // In Opera 10.10 and earlier, if the mouse is over this widget, the cursor change won't
        // be picked up unless we force a refresh of the HTML (or the user mouses off, then back
        // on the widget).
        
        if (isc.Browser.isOpera && isc.Browser.minorVersion < 10.5 && this === isc.EH.lastEvent.target) {
            this.markForRedraw();
        }
    }
},

_updateCursor : function() {
    var currentCursor = this.getCurrentCursor();

    // apply the appropriate cursor to the Canvas
    
    this._applyCursor(currentCursor);
},


getCurrentCursor : function () {
    // If we don't have a special cursor, we need to show the original cursor.
    var currentCursor = this.cursor;

    // If setNoDropIndicator has been called, show the "no-drop" cursor when the user
    // drags over this widget.
    // Takes presidence over other custom cursors (EG even if disabled we should show this
    // no-drop indicator when the user drags over us).
    
    if (isc.EH.dragging && this._noDropIndicatorSet && (isc.EH.dragMoveTarget != this)) {
        currentCursor = this.noDropCursor;

    // If we're disabled, let the disabled cursor show
    } else if (this.isDisabled()) currentCursor = this.disabledCursor;

    // Drag indicators
    else {
        // Edge drag resizing
        var edgeCursor;
        if (this.canDragResize && this.edgeCursorMap) {
            // if this Canvas is resizable and there's an edgeCursorMap
            // determine whether we're on the edge, and show the appropriate cursor
            var edge = this.getEventEdge();
            if (edge && this.edgeCursorMap[edge]) {
                currentCursor = this.edgeCursorMap[edge];
                edgeCursor = true;
            }
            //this.logWarn("over edge: " + edge + " with cursor: " + currentCursor);
        }
        // drag repositioning
        if (!edgeCursor && this.canDragReposition && this.dragRepositionCursor) {
            currentCursor = this.dragRepositionCursor;
        }
    }

    return currentCursor;
},


// Hover handling

//> @method canvas.getHoverTarget() (A)
// This method is fired when a user moves over this widget, and returns a pointer to the widget
// that should receive a hover event if the user remains positioned over this canvas.
// Default implementation will return the first ancestor of this widget (or this widget itself)
// for which <code>canHover</code> is true.  If it encounters a parent for which canHover is
// explicitly set to false, the default implementation returns null.
//  @group hovers
//  @visibility internal
//  @param event (event) Current mouse event.
//  @return (Canvas) Hover target for the current event (or null)
//<
getHoverTarget : function (event, eventInfo) {
    var target = this;
    while (target) {
        var canHover = target.getCanHover();
        if (canHover == null) {
            // If the target has a prompt specified this implies the developer wants hover
            // behavior to show the prompt on that target.
            if (target.prompt != null) return target;
            target = target.parentElement;
        } else if (canHover) {
            return target;
        } else {
            return null;
        }
    }
    return null;
},

//> @method canvas.startHover() (A)
// Handler fired when the mouse goes over a valid hover target, or some other canvas which
// identifies this as the hover target.  Starts the hover timer to fire public hover handling
// methods on the hoverTarget.
//  @group hovers
//  @visibility internal
//  @param event (event) Current mouse event.
//<
startHover : function (event) {
    isc.Hover.setAction(this, this._handleHover, null, this.hoverDelay);
},

//> @method canvas.stopHover() (A)
// Handler fired when the mouse leaves a hover target.
// Clears the hover timer to fire public hover handling methods on the target.
//  @group hovers
//  @visibility internal
//  @param event (event) Current mouse event.
//<

stopHover : function (event) {
    isc.Hover.clear();
},

// Internal method fired when the hover timer returns - will fire exposed hover handler methods.
_handleHover : function () {
    //!DONTCOMBINE
    var EH = isc.EH,
        lastMoveTarget = EH.lastMoveTarget;
    // Catch the case wher the user has moved out of this canvas
    
    var event = EH.lastEvent;
    if (!lastMoveTarget || lastMoveTarget.getHoverTarget(event) != this) return;

    return this.handleHover();
},

// getCanHover() - should this canvas fire hover events?
getCanHover : function () {
    return this.canHover;
},

//> @attr canvas.showHoverComponents (Boolean : false : IRWA)
// When set to true, shows a widget hovering at the mouse point instead of the builtin
// hover label.  Override +link{canvas.getHoverComponent, getHoverComponent} to provide the
// Canvas to show as the hoverComponent.
// @group hoverComponents
// @visibility external
//<

//> @method canvas.getHoverComponent()
// When +link{showHoverComponents} is true, this method is called to get the component to show
// as a hover for this Canvas.  There is no default implementation of this method, so you need
// to override it with an implementation that returns a Canvas that suits your needs.
// <P>
// By default, components returned by <code>getHoverComponent()</code> will not be
// automatically destroyed when the hover is hidden.  To enforce this, set
// +link{canvas.hoverAutoDestroy} to true on the returned component.
//
// @return (Canvas | Canvas Properties) the component to show as a hover
// @group hoverComponents
// @visibility external
//<
getHoverComponent : function () {
},

//> @method canvas.handleHover() (A)
// Handler fired on a delay when the user hovers the mouse over this hover-target.
// Default implementation will fire <code>this.hover()</code> (if defined), and handle
// showing the hover canvas if <code>this.showHover</code> is true.
//  @group hovers
//  @visibility external
//  @see canvas.canHover
//  @see canvas.showHover
//  @see canvas.hover()
//<
handleHover : function () {
    if (this.hover && this.hover() == false) return;
    if (this.showHover) {
        var component = this.showHoverComponents && this.getHoverComponent ? this.getHoverComponent() : null;
        if (component != null && isc.isA.Canvas(component)) {
            //isc.logWarn("canvas: "+this.getID()+" - showing hoverCanvas: "+this.hoverCanvas.getID());
            // getHoverComponent() returned a Canvas - we'll show that now instead of the
            // hoverHTML in a Label but using the same positioning/sizing logic
            var hoverProperties = this._getHoverProperties();
            isc.Hover.show(component, hoverProperties, null, this);
        } else {
            var HTML = this.getHoverHTML();
            if (HTML != null && !isc.isAn.emptyString(HTML)) {
                var hoverProperties = this._getHoverProperties();
                isc.Hover.show(HTML, hoverProperties, null, this);
            }
        }
    }
},



//> @method canvas.updateHover() (A)
// If this canvas is currently showing a hover (see +link{canvas.handleHover}), this method
// can be called to update the contents of the hover. Has no effect if the hover canvas is not
// showing for this widget.
//  @param [hoverHTML] (string) Option to specify new HTML for the hover. If not passed, the result
//   of +link{canvas.getHoverHTML(),this.getHoverHTML()} will be used instead. Note that if the
//   hover HTML is empty the hover will be hidden.
//  @group hovers
//  @visibility external
//<
updateHover : function (hoverHTML) {
    if (isc.Hover.lastHoverCanvas != this || !isc.Hover.hoverCanvas.isVisible()) return;
    if (hoverHTML == null) hoverHTML = this.getHoverHTML();
    isc.Hover.show(hoverHTML,  this._getHoverProperties(), null, this);
},

_hoverHidden : function () {
    // if we have a local reference to the hoverCanvas, it's a canvas we've returned from
    // getHoverComponent, rather than the built-in label from isc.Hover.  If it's been flagged
    // for auto-destruction on hide, do that now...
    if (this.hoverCanvas && this.hoverCanvas.hoverAutoDestroy != false) {
        //isc.logWarn("canvas: "+this.getID()+" - auto-destroying hoverCanvas: "+this.hoverCanvas.getID());
        this.hoverCanvas.markForDestroy();
        this.hoverCanvas = null;
        delete this.hoverCanvas;
    }

    this.hoverHidden();
},

//> @method canvas.hoverHidden() (A)
// If +link{canvas.showHover,showHover} is true for this canvas, this notification method will be
// fired whenever the hover shown in response to +link{canvas.handleHover(),handleHover()} is
// hidden. This method may be observed or overridden.
// @group hovers
// @visibility external
//<
hoverHidden : function () {},

// Helper method to assemble the properties to apply to the hover canvas into an object to pass
// to Hover.show()
_getHoverProperties : function () {
    var target = isc.EH.getTarget(),
        parent = target ? isc.isA.CanvasItem(target) ? target : target.canvasItem : null,
        result
    ;

    if (parent) {
        result = {
            width: (parent.hoverWidth != null ? parent.hoverWidth : this.hoverWidth),
            height: (parent.hoverHeight != null ? parent.hoverHeight : this.hoverHeight),
            align: (parent.hoverAlign != null ? parent.hoverAlign : this.hoverAlign),
            valign: (parent.hoverVAlign != null ? parent.hoverVAlign : this.hoverVAlign),
            baseStyle: (parent.hoverStyle != null ? parent.hoverStyle : this.hoverStyle),
            opacity: (parent.hoverOpacity != null ? parent.hoverOpacity : this.hoverOpacity),
            moveWithMouse: (parent.hoverMoveWithMouse != null ?
                        parent.hoverMoveWithMouse : this.hoverMoveWithMouse),
            wrap: (parent.hoverWrap != null ? parent.hoverWrap : this.hoverWrap)
        };
        return result;
    }


    return {    width:this.hoverWidth, height:this.hoverHeight, align:this.hoverAlign,
                valign:this.hoverVAlign, baseStyle:this.hoverStyle, opacity:this.hoverOpacity,
                moveWithMouse:this.hoverMoveWithMouse, wrap:this.hoverWrap
           };
},

//> @method canvas.hover()
// If <code>canHover</code> is true for this widget, the <code>hover</code> string method will
// be fired when the user hovers over this canvas. If this method returns false, it will
// suppress the default behavior of showing a hover canvas if <code>this.showHover</code>
// is true.
//  @return (boolean) false to cancel the hover event.
//  @group hovers
//  @see canvas.canHover
//  @visibility external
//<


//> @method canvas.getHoverHTML()
// If <code>this.showHover</code> is true, when the user holds the mouse over this Canvas for
// long enough to trigger a hover event, a hover canvas is shown by default. This method returns
// the contents of that hover canvas. Default implementation returns <code>this.prompt</code> -
// override for custom hover HTML. Note that returning <code>null</code> or an empty string will
// suppress the hover canvas altogether.
//  @group hovers
//  @see canvas.showHover
//  @return (HTMLString) the string to show in the hover
//  @visibility external
//<
getHoverHTML : function () {
    return this.prompt;
},

// CSS Style methods
// ------------------------------------------------------------------------------------------------------

//>	@method	canvas.setClassName()   ([A])
// Sets the CSS class for this widget
//      @visibility external
//		@group	appearance
//		@param	newClass		(string)	new CSS class name (must be defined previously)
//  @deprecated As of SmartClient version 5.5, use +link{canvas.setStyleName()} instead.
//<
_$styleName:"styleName",
setClassName : function (newClass) {
    // we expect this to happen a lot as we haven't converted all internal usage of .className
    // Log a warning at the info level only
    if (this.logIsInfoEnabled(this._$styleName)) {
        this.logInfo("call to deprecated setClassName() property - use setStyleName() instead");
    }
    return this.setStyleName(newClass);
},

//>	@method	canvas.setStyleName()
// Sets the CSS class for this widget
// @group appearance
// @param newStyle (CSSStyleName) new CSS style name
// @visibility external
// @example styles
//<
setStyleName : function (newStyle) {

    this._cachedBorderSize = null;
    this._cachedPadding = null;

    
    this._childrenCoordsChanged();


    if (newStyle) {
        this.styleName = newStyle;
        // Also update the depreacted className property
        this.className = newStyle;
    }

    if (this.getClipHandle()) this.getClipHandle().className = this.styleName;

    
    if (this.overflow != isc.Canvas.HIDDEN) {

        
        if (this.overflow == isc.Canvas.VISIBLE) this._resetHandleOnAdjustOverflow = true;
        this.adjustOverflow("setStyleName");
     }

},

//>	@method	canvas.getStateName()	(A)
//			get the CSS class for a particular canvas (not what it is, what it should be)
//			OVERRIDE to implement some other scheme
//		@group	appearance
//
//		@return	(CSSStyleName)	name of the style to set the canvas to
//<
getStateName : function () {
	var handleClassName = this.getClipHandle() ? this.getClipHandle().className : null;

	return (handleClassName != null ? handleClassName : this.styleName);
},


// Context Menu Handling
// ------------------------------------------------------------------------------------------------

// Internal contextMenu event handler to fire partwise events if appropriate.
handleShowContextMenu : function (event) {
    if (event.target == this && this.useEventParts) {
        var partInfo = this.getEventPart(event);
        // Fire showCM for appropriate part
        if (partInfo.part) {
            if (this._firePartEvent(partInfo.part,
                                    "showContextMenu",
                                    partInfo.element,partInfo.ID,event) == false) return false;
        }
    }
    if (this.showContextMenu) return this.showContextMenu(event);

},

//>	@method	canvas.showContextMenu()	(A)
// Executed when the right mouse button is clicked.  The default implementation of
// this method auto-creates a +link{class:Menu} from the +link{attr:canvas.contextMenu} property on
// this component and then calls +link{method:menu.showContextMenu} on it to show it.
// <p>
// If you want to show a standard context menu, you can simply define your Menu and set it as the
// contextMenu property on your component - you do not need to override this method.
// <p>
// If you want to do some other processing before showing a menu or do something else entirely, then
// you should override this method.  Note that the return value from this method controls whether or
// not the native browser context menu is shown.
//
// @return (boolean)	false == don't show native context menu, true == show native context menu
// @group widgetEvents
// @see attr:contextMenu
// @see method:menu.showContextMenu
// @see method:canvas.hideContextMenu
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
// @platformNotes
// On the Mac platform, context menu functionality may be triggered by <code>Command+click</code><br>
// On the Opera browser, context menu functionality may be triggered by <code>Shift+Ctrl+click</code>
//<
showContextMenu : function () {
    var menu = this.contextMenu;
	if (menu) {
		menu.target = this;
        if (!isc.isA.Canvas(menu)) {
            menu.autoDraw = false;
            this.contextMenu = menu = this.getMenuConstructor().create(menu);
        }
		menu.showContextMenu();
	}
	return (menu == null);
},

getMenuConstructor : function () {
    var menuClass = isc.ClassFactory.getClass(this.menuConstructor);
    if (!menuClass) {
        isc.logWarn("Class not found for menuConstructor:" + this.menuConstructor +
            ". Defaulting to isc.Menu class");
        menuClass = isc.ClassFactory.getClass("Menu");
    }
    return menuClass;
},

//>	@method	canvas.hideContextMenu()	(A)
//
// The default implementation of this method hides the contextMenu currently being shown for this
// component (which occurs when the mouse button that toggles the context menu is released).
// Override if you want some other behavior.
//
// @see showContextMenu()
// @see menu.hideContextMenu()
// @group	widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
//<
hideContextMenu : function (){
	if (this.contextMenu) this.contextMenu.hideContextMenu();
},

// Mouse Events
// ------------------------------------------------------------------------------------------------------

//> @method canvas.mouseOver() (A)
//
// Executed when mouse enters this widget.  No default implementation.
//
// @return (Boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
//<


//> @method canvas.mouseDown() (A)
//
// Executed when the left mouse down is pressed on this widget.  No default implementation.
//
// @platformNotes If the end user system has only one mouse button, then it is considered the "left"
//                mouse button (and this method would execute when it is pressed on this widget).
//
// @return (Boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
//<

// _allowNativeTextSelection.
// By default returns this.canSelectText

_allowNativeTextSelection : function (event) {
    return this.canSelectText;
},

//> @method canvas.rightMouseDown() (A)
//
// Executed when the right mouse down is pressed on this widget.  No default implementation.
//
// @platformNotes Some end user systems only have one mouse button.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
//<


//> @method canvas.mouseStillDown() (A)
//
// Executed repeatedly (every +link{attr:canvas.mouseStillDownDelay} by default) when the system is idle -
// i.e. not busy running other scripts - and the left mouse button is held down after having been
// pressed in the object. This event is not native to JavaScript, but is provided by the ISC system.
// <p>
// Note: The event handling system waits +link{attr:canvas.mouseStillDownInitialDelay} before
// calling mouseStillDown for the first time on the widget.  Subsequently the method is called every
// +link{attr:canvas.mouseStillDownDelay}.  Both attributes are configurable per widget instance.
// <p>
// This method is called only when the left mouse is held down.
//
// @platformNotes Some end user systems only have one mouse button.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see mouseStillDownInitialDelay
// @see mouseStillDownDelay
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
// @example customMouseEvents
//<


//> @method canvas.mouseMove() (A)
//
// Executed when the mouse moves within this widget.  No default implementation.
//
// @return (Boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
// @example customMouseEvents
//<


//> @method canvas.mouseOut() (A)
//
// Executed when the mouse leaves this widget.  No default implementation.
//
// @return (Boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @visibility external
// @example customMouseEvents
//<



//> @method canvas.mouseUp() (A)
//
// Executed when the left mouse is released on this widget.  No default implementation.
//
// @return (Boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
// @example customMouseEvents
//<


//> @method canvas.click() (A)
//
// Executed when the left mouse is clicked (pressed and then released) on this widget.  No default
// implementation.
//
// @return (Boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
//<


//> @method canvas.doubleClick() (A)
//
// Executed when the left mouse button is clicked twice in rapid succession (within
// +link{attr:Canvas.doubleClickDelay} by default) in this object.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @see doubleClickDelay
// @group widgetEvents
// @visibility external
//<

//> @method canvas.mouseWheel() (A)
//
// Executed when the mouse wheel is actuated.
//
// @platformNotes Not all end user systems have mouse wheels.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see EventHandler.getWheelDelta()
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
// @example customMouseEvents
//<

// Partwise mouse event handling
// ------------------------------------------------------------------------------------------------------
// In some cases we want to be able to respond to events over HTML elements written into our handle
// (For example the 'drawRect()' / 'rectMouseMove()' et al system).
// We handle this by writing an attribute 'eventPart' onto the elements in question and treating
// events occurring over these elements specially (firing custom handlers depending on the
// part name).
// This is all disabled by default - enable by flipping the 'useEventParts' attribute

// Implement handleMouseMove to fire part-wise events if we're configured to do so and the
// user is over a 'part' type element
handleMouseMove : function (event, eventInfo) {
    if (event.target == this && this.useEventParts) {
        var partInfo = this.getEventPart(event),
            lastOverPart = this._lastOverPart;

        // If we moved out of a part, fire a [part]Out handler
        if (lastOverPart &&  lastOverPart.part &&
            (lastOverPart.part != partInfo.part || lastOverPart.ID != partInfo.ID))
        {
            this._firePartEvent(lastOverPart.part, isc.EH.MOUSE_OUT,
                                lastOverPart.element, lastOverPart.ID, event);
        }

        // Fire over or move handler on the new part
        if (partInfo.part) {

            var newPart = !lastOverPart || (lastOverPart.ID != partInfo.ID),
                eventType = (newPart ? isc.EH.MOUSE_OVER : isc.EH.MOUSE_MOVE)
            ;

            this._firePartEvent(partInfo.part, eventType, partInfo.element,partInfo.ID,event);

            // If this is a new part, we want to start the part-wise hover timer
            
            if (newPart) {
                isc.Hover.setAction(this, this._handleRectHover, [partInfo.element, partInfo.ID], this.hoverDelay);
            }

        }

        this._lastOverPart = partInfo;
    }

    if (this.mouseMove) return this.mouseMove(event, eventInfo);
},

// Handle a hover on a rect written out by the drawRect() system
_handleRectHover : function (element, ID) {
    //!DONTCOMBINE
    if (this._lastOverPart) this._firePartEvent(this._lastOverPart.part, "hover", element, ID);
},

// Implement handleMouseOut to trip the part-wise mouseOut handler if we're firing
// partwise events, and the user is moving off an event part.
handleMouseOut : function (event, eventInfo) {
    if (event.target == this && this.useEventParts) {
        var lastOverPart = this._lastOverPart;
        if (lastOverPart && lastOverPart.part) {
            this._firePartEvent(lastOverPart.part, isc.EH.MOUSE_OUT,
                                lastOverPart.element, lastOverPart.ID, event);
        }
    }
    if (this.mouseOut) return this.mouseOut(event, eventInfo);
},

// Implement handle mouseDown, mouseUp, click and doubleClick to fire partwise events if
// appropriate
handleMouseDown : function (event, eventInfo) {
    // cancel any momentum scrolling from drag scrolling
    var animationId = this._momentumScrollId;
    if (animationId != null) {
        this.cancelAnimation(animationId);
    }

    if (event.target == this && this.useEventParts) this.firePartEvent(event, isc.EH.MOUSE_DOWN);
    if (this.mouseDown) return this.mouseDown(event, eventInfo);
},

handleMouseUp : function (event, eventInfo) {
    if (event.target == this && this.useEventParts) this.firePartEvent(event, isc.EH.MOUSE_UP);
    if (this.mouseUp) return this.mouseUp(event, eventInfo);
},

handleClick : function (event, eventInfo) {
    if (event.target == this && this.useEventParts) this.firePartEvent(event, isc.EH.CLICK);
    if (this.click) return this.click(event, eventInfo);
},

handleDoubleClick : function (event, eventInfo) {
    if (event.target == this && this.useEventParts) this.firePartEvent(event, isc.EH.DOUBLE_CLICK);
    if (this.doubleClick) return this.doubleClick(event, eventInfo);
},

// "longTouch" event - fired by touch browsers when the user holds their finger over a
// widget.
// By default this is wired into the context click event system
handleLongTouch : function (event, eventInfo) {
    return this.handleShowContextMenu(event, eventInfo);
},

_$eventPart:"eventpart",
//> @method canvas.getEventPart()
// If this canvas is using partwise events, given an event determine which part it occurred over
// @visibility eventParts
//<
getEventPart : function (event) {
    if (!event) event = isc.EH.lastEvent;
    var part = event.eventPart,
        partID = event.eventPartID;

    if (partID == isc.emptyString) {
        partID = null;

    // part elements' IDs are expected to be of the form <widgetID>_partType_partID
    } else if (partID != null) {
        partID = partID.substring(this.getID().length + part.length + 2);
    }
    var partInfo = {part:part, ID:partID, element:event.nativeTarget};
    return partInfo;
},


getElementPart : function (element) {
    var part, partID;
    if (this.isDrawn() && element != null) {
        var handle = this.getHandle();
        
        if (isc.isA.Canvas(element)) element = element.getHandle();
        while (element != handle && element != null) {
            if (element.getAttribute) {
                part = element.getAttribute(this._$eventPart);
            }
            if (part == null || part == isc.emptyString) {
                element = element.parentNode;
            } else {
                break;
            }
        }

        if (part && part != isc.emptyString) {
            var elementID = element.id;
            if (elementID && elementID != isc.emptyString) {
                partID = elementID.substring(this.getID().length + part.length + 2);
            }
        }
    }

    return {part:part, ID:partID, element:element};
},

// For the AutoTest APIs, we need to be able to get back at the eventPart handle from the part
// name.

getPartElement : function (partObj) {
    // check for standardized element ID first
    var part = partObj.part,
        partID = partObj.partID,
        elementID = this.getID() + "_" + part;
    if (partID) elementID += partID;
    var element = isc.Element.get(elementID);
    if (element) return element;

    // If we didn't find it do an iteration through our descendent nodes
    return isc.Element.findAttribute(this.getHandle(), this._$eventPart, part);
},

// Given a generic event on this widget, determine whether it occurred over a specific 'part'
// If so, fire the appropriate part event.
firePartEvent : function (event, eventType) {
    if (!this.useEventParts || !event) return;
    var partInfo = this.getEventPart(event);
    if (!partInfo.part) return;

    if (!eventType) eventType = event.eventType;

    return this._firePartEvent(partInfo.part, eventType, partInfo.element, partInfo.ID, event);
},

// _firePartEvent() - helper to fire <partName>MouseOver() et al.
_firePartEvent : function (partName, eventType, element,ID,event) {
    var handlerName = this.getPartEventHandler(partName, eventType);

    if (this[handlerName]) {
        return this[handlerName](element,ID,event);
    }
},

//> @method canvas.getPartEventHandler()
// Given a part name and an event type, this method returns the name of the part-wise event
// handler to fire (such as "rectMouseOut")
// @visibility eventParts
//<
getPartEventHandler : function (partName, event) {

    if (!isc.Canvas._partHandlers[partName]) isc.Canvas._partHandlers[partName] = {};
    if (!isc.Canvas._partHandlers[partName][event]) {

        // We will fire [partName]MouseOver, [partName]Click, etc.
        var suffix = event.substring(0,1).toUpperCase() + event.substring(1);
        isc.Canvas._partHandlers[partName][event] = partName + suffix;
    }

   return isc.Canvas._partHandlers[partName][event];
},


// Drag and Drop
// ------------------------------------------------------------------------------------------------------

//> @method canvas.dragRepositionStart() (A)
//
// Executed when dragging first starts. No default implementation.  Create this handler to set
// things up for the drag reposition.
//
// @return (boolean) false to cancel the drag reposition action
// @group widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
//<


//> @method canvas.dragRepositionMove() (A)
//
// Executed every time the mouse moves while drag-repositioning. If this method
// does not return false, the +link{attr:canvas.dragTarget} (or outline if
// +link{attr:canvas.dragAppearance} is set to "outline") will automatically be moved as appropriate
// whenever the mouse moves.
//
// @return (boolean) false to suppress auto-move of the +link{attr:canvas.dragTarget} or outline.
// @group widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
//<


//> @method canvas.dragRepositionStop() (A)
//
// Executed when the mouse button is released at the end of the drag. Your
// widget can use this opportunity to fire custom code based upon where the
// mouse button was released, etc.
// <p>
// Returning true from this handler will cause the +link{attr:canvas.dragTarget} (or outline if
// +link{attr:canvas.dragAppearance} is set to "outline") to be left in its current
// location. Returning false from this handler will cause it to snap back to its
// original location.
//
// @return (boolean) false to snap the +link{attr:canvas.dragTarget} (or outline) back to its
//                   original location or true to leave it at the current cursor position.
// @group widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
//<

//> @method canvas.dragResizeStart() (A)
//
// Executed when resize dragging first starts. No default implementation.
// Create this handler to set things up for the drag resize.
//
// @return (boolean) false to cancel the drag reposition action
// @group widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
//<


//> @method canvas.dragResizeMove() (A)
//
// Executed every time the mouse moves while drag-resizing. If this method
// does not return false, the +link{attr:canvas.dragTarget} (or outline if
// +link{attr:canvas.dragAppearance} is set to "outline") will automatically be moved as appropriate
// whenever the mouse moves.
//
// @return (boolean) false to suppress auto-resize of the +link{attr:canvas.dragTarget} or outline.
// @group widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
//<


//> @method canvas.dragResizeStop() (A)
//
// Executed when the mouse button is released at the end of the drag resize. Your
// widget can use this opportunity to fire custom code based upon where the
// mouse button was released, etc.
// <p>
// Returning true from this handler will cause the +link{attr:canvas.dragTarget} (or outline if
// +link{attr:canvas.dragAppearance} is set to "outline") to be left at its current size. Returning
// false from this handler will cause it to snap back to its original location size
//
// @return (boolean) false to snap the +link{attr:canvas.dragTarget} (or outline) back to its
//                   original size or true to leave it at the current cursor position.
// @group widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
//<


//> @method canvas.dragStart() (A)
// Executed when dragging first starts. Your widget can use this opportunity to set
// things up for the drag, such as setting the drag tracker. Returning false from this
// event handler will cancel the drag action entirely.
// <p>
// A drag action is considered to be begin when the mouse has moved
// +link{attr:canvas.dragStartDistance} with the left mouse down.
//
// @return (boolean) false to cancel drag action.
// @group widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
// @example dragPan
//<


//> @method canvas.dragMove() (A)
// Executed every time the mouse moves while dragging this canvas.
//
// @return (boolean) false to cancel drag interaction.
// @group widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
// @example dragPan
//<


//> @method canvas.dragStop() (A)
// Executed when the mouse button is released at the end of the drag. Your widget can
// use this opportunity to fire code based on the last location of the drag or reset any
// visual state that was sent.
//
// @return (boolean) false to cancel drag interaction.
// @group widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
//<


//> @method canvas.dropOver() (A)
//
// Executed when the compatible dragged object is first moved over this drop target. Your
// implementation can use this to show a custom visual indication that the object can be
// dropped here.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
// @example customDrag
//<

//> @method canvas.dropMove() (A)
//
// Executed whenever the compatible dragged object is moved over this drop target. You
// can use this to show a custom visual indication of where the drop would occur within the
// widget.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
// @example customDrag
//<


//> @method canvas.dropOut() (A)
//
// Executed when the dragged object is moved out of the rectangle of this drop target. If you
// have set a visual indication in dropOver or dropMove, you should reset it to its normal
// state in dropOut.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @visibility external
// @example customDrag
//<



//> @method canvas.drop() (A)
//
// Executed when the mouse button is released over a compatible drop target at the end of
// a drag sequence. Your widget should implement whatever it wants to do when receiving a
// drop here. For example, in a file moving interface, a drop might mean that you should
// move or copy the dragged file into the folder it was dropped on, or dropping something in
// a trash can might mean to clear it from the screen.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
//
// @group widgetEvents
// @see method:canvas.getOffsetX
// @see method:canvas.getOffsetY
// @see EventHandler.getDragTarget()
//
// @visibility external
// @example dragCreate
//<


// Keyboard handling
// ------------------------------------------------------------------------------------------------------


//> @method canvas.keyDown() (A)
//
// Executed when a key is pressed on a focusable widget (+link{attr:canvas.canFocus}: true).
// <P>
// Use +link{EventHandler.getKey()} to find out the +link{type:KeyName,keyName} of the key that
// was pressed, and use +link{EventHandler.shiftKeyDown()} and related functions to determine
// whether modifier keys were down.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see canFocus
// @visibility external
//<


//> @method canvas.keyUp() (A)
//
// Executed when a key is released on a focusable widget (+link{attr:canvas.canFocus}: true).
// <P>
// Use +link{EventHandler.getKey()} to find out the +link{type:KeyName,keyName} of the key that
// was pressed, and use +link{EventHandler.shiftKeyDown()} and related functions to determine
// whether modifier keys were down.
//
// @return (Boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see canFocus
// @visibility external
//<


//> @method canvas.keyPress() (A)
//
// Executed when a key is pressed and released on a focusable widget (+link{attr:canvas.canFocus}:
// true).
// <P>
// Use +link{EventHandler.getKey()} to find out the +link{type:KeyName,keyName} of the key that
// was pressed, and use +link{EventHandler.shiftKeyDown()} and related functions to determine
// whether modifier keys were down.
//
// @return (boolean) false to suppress native behavior in response to the keyPress, and prevent
//                   this event from bubbling to this widget's parent, or true or undefined to bubble.
// @group widgetEvents
// @see canFocus
// @visibility external
// @example keyboardEvents
//<



//>	@method	canvas.getDragType()	(A)
// Return the type of stuff that was dragged from this object
//
//		@return	(DragTypes)
// @group dragdrop
//<
getDragType : function () {
	return this.dragType;
},


//>	@method	canvas.willAcceptDrop()	[A]
//
// Returns true if the widget object being dragged can be dropped on this widget, and
// false otherwise.  The default implementation of this method simply compares the
// +link{Canvas.dragType} of the <code>dragTarget</code> (the component being dragged from)
// with the list of +link{Canvas.dropTypes} on this Canvas.  If the +link{Canvas.dropTypes}
// list contains the +link{Canvas.dragType} value, then this method returns true.  Otherwise it
// returns false.
//
// @return	(boolean)	true if the widget object being dragged can be dropped on this widget,
//                      false otherwise
//
// @see Canvas.dragType
// @see Canvas.dropTypes
// @see Canvas.dragTarget
// @see Canvas.drop
//
// @group dragdrop
// @visibility external
//<
willAcceptDrop : function () {

    // if nothing is currently being dragged, return false
    if (this.ns.EH.dragTarget == null) return false;

	// if the dropTypes of this object is not set,
	// 	assume we can take anything...
	if (this.dropTypes == isc.Canvas.ANYTHING || this.dropTypes == null ||
        isc.is.emptyString(this.dropTypes))
    {
        return true;
    }

	// get the type of stuff that's being dragged
	var type = this.ns.EH.dragTarget.getDragType();
	// if the object being dragged has no dragType, assume we can't take it
	if (type == null || isc.is.emptyString(type)) return false;

	// otherwise go based on the type of the drag types
	if (isc.isA.String(type)) {
		// if it's a string, return true if our dropTypes contains the type
		return this.dropTypes.contains(type);
	} else if (isc.isAn.Array(type)) {
		// if it's an array, return true if our dropTypes contains all sub-types
		for (var i = 0, OK = true, length = type.length; i < length  && OK; i++) {
			OK = OK && (this.dropTypes.contains(type));
		}
		return OK;
	}

	// otherwise assume we can't take it
	return false;
},


_showDragMask : function () {
    // show() the eventMask canvas if it's hidden
    // Note: check _eventMask.visibility rather than eventMask.isVisible() because of the
    // case where a parent is hidden
    if (this._eventMask.visibility == isc.Canvas.HIDDEN) this._eventMask.show();
},

_hideDragMask : function () {
    // Note: check _eventMask.visibility rather than eventMask.isVisible() because of the
    // case where a parent is hidden
    if (this._eventMask.visibility != isc.Canvas.HIDDEN) this._eventMask.hide();
},

// handleDrop() -- if 'onDrop' exists fire this before the standard drop behavior

handleDrop : function (event,eventInfo) {
    if (this.onDrop != null && (this.onDrop() == false)) return false;
    return this.drop(event,eventInfo);
},



// Drag/drop snap-to-grid functionality

//>	@method	canvas.getHSnapPosition()	(A)

// Override this method to provide a custom snap-to grid.  Note that you do not need to do
// this if your grid is regular (ie, grid points are every x pixels); regular grids should be
// defined using +link{canvas.snapHGap} and +link{canvas.snapVGap}.
// You should only override this method if you want to provide support for a grid of
// irregularly-placed points
//
// @param coordinate (int) x-coordinate of the drag event relative to the inside of this widget
// @param [direction] (string) "before" or "after" denoting whether the returned coordinate should
//   match the left or right edge of the current square. If unset +link{canvas.snapHDirection} will
//   be used by default
// @return (int) The horizontal coordinate to snap to
// @group dragdrop
// @visibility external
//<
getHSnapPosition : function (coordinate, direction) {
    if (! direction) {
        direction = this.snapHDirection;
    }
    if (direction != isc.Canvas.BEFORE &&
        direction != isc.Canvas.AFTER  &&
        direction != isc.Canvas.NEAREST) {
        // log an error and return the supplied coord
        return coordinate;
    }

    var before = Math.floor(coordinate / this.snapHGap) * this.snapHGap;
    var after  = before + this.snapHGap;
    var halfway = before + this.snapHGap / 2;

    if (direction == isc.Canvas.BEFORE) {
        return before;
    } else if (direction == isc.Canvas.AFTER) {
        return after;
    } else {
        // If we're exactly inbetween, go left
        if (coordinate <= halfway) return before;
        else return after;
    }

},

//>	@method	canvas.getVSnapPosition()	(A)
// Override this method to provide a custom snap-to grid.  Note that you do not need to do
// this if your grid is regular (ie, grid points are every x pixels) - regular grids should be
// defined using +link{canvas.snapHGap} and +link{canvas.snapVGap}.
// You should only override this method if you want to provide support for a grid of
// irregularly-placed points
//
// @param coordinate (int) y-coordinate of the drag event relative to the inside of this widget
// @param [direction] (string) "before" or "after" denoting whether the returned coordinate should
//   match the top or bottom edge of the current square. If unset +link{canvas.snapHDirection} will
//   be used by default
// @visibility external
//  @group dragdrop
//  @return (int) The vertical coordinate to snap to
//<
getVSnapPosition : function (coordinate, direction) {
    if (! direction) {
        direction = this.snapVDirection;
    }
    if (direction != isc.Canvas.BEFORE &&
        direction != isc.Canvas.AFTER  &&
        direction != isc.Canvas.NEAREST) {
        // log an error and return the supplied coord
        return coordinate;
    }

    var before = Math.floor(coordinate/ this.snapVGap) * this.snapVGap;

    var after  = before + this.snapVGap;
    var halfway = before + this.snapVGap / 2;

    if (direction == isc.Canvas.BEFORE) {
        return before;
    } else if (direction == isc.Canvas.AFTER) {
        return after;
    } else {
        // If we're exactly inbetween, go up
        if (coordinate <= halfway) return before;
        else return after;
    }
},

//>	@method	canvas.shouldSnapOnDrop()	(A)
// Override this method to give programmatic control over whether or not the parameter
// <code>dragTarget</code> should snap to this object's grid when dropped.  Note that this only applies
// if snap-to-grid is enabled on either <code>dragTarget</code> or this object.  See
// +link{canvas.snapToGrid} and +link{canvas.childrenSnapToGrid}.
// <P>
// The default implementation simply returns true.
//
// @visibility external
// @group dragdrop
// @param dragTarget (isc.Canvas) The object about to be dropped
// @return (boolean) true if <code>dragTarget</code> should snap to this object's grid; otherwise false
//<
shouldSnapOnDrop : function (dragTarget) {
    return true;
},

// internal helper to suppress drag offsets when dragging child in snapToGrid mode
noSnapDragOffset : function (dragTarget) {
    return false;
},


// Images
// ------------------------------------------------------------------------------------------------------

//>	@method	canvas.setAppImgDir()
// Set the image directory for this individual widget.
// @group images
// @param	[URL]	(URL)	URL (relative to Page.appImgDir) for where images for this widget live
//<
setAppImgDir : function (URL) {
	if (URL) this.appImgDir = URL;
},

//>	@method	canvas.getAppImgDir()
// Return the image directory for this individual widget, prepended with the Page image directory.
// @group images
// @return	(URL)	Image directory (including Page image directory) for this widget.
//<
getAppImgDir : function () {
	return isc.Page.getImgURL("", this.appImgDir);
},

//>	@method	canvas.setSkinImgDir()
// Set the widget image directory for this individual widget.
// @group images
// @param	[URL]	(URL)	URL (relative to Page.appImgDir) for where images for this widget live
//<
setSkinImgDir : function (URL) {
	if (URL) this.skinImgDir = URL;
},

//>	@method	canvas.getSkinImgDir()
// Return the widget image directory for this individual widget, prepended with the Page image
// directory.
//
// @group images
// @return	(URL)	Widget image directory (including Page widget image directory) for this widget.
//<
getSkinImgDir : function () {
	return isc.Page.getSkinImgDir(this.skinImgDir);
},


//>	@method	Canvas.getImgURL() (A)
// Return the full URL for an image to be drawn in this canvas.
// <P>
// If the passed URL begins with the special prefix "[SKIN]", it will have the
// widget.skinImgDir and Page.skinImgDir prepended.  Otherwise the image is assumed to be
// application-specific, and will have the widget.appImgDir and Page.appImgDir automatically
// prepended.
// <P>
// Note that if passed an absolute path (starting with "/" or "http://" for example), no extra
// image directory information will be prepended to the generated URL.//
//
// @param URL      (string) URL local to skin or application image directory
// @param [imgDir] (string) optional image directory to override the default for this Canvas
// @group images
// @return (string) URL to use
// @visibility external
//<
getImgURL : function (src, imgDir) {
    return isc.Canvas.getImgURL(src, imgDir, this);
},


//> @object ImgProperties
//
// A set of properties that can be used to create an image.
//
// @treeLocation Client Reference/Foundation/Canvas
// @visibility external
//<

//> @attr imgProperties.src (URL : null : IRW)
//
// Specifies the URL of the image local to the skin or application directory.
//
// @visibility external
//<

//> @attr imgProperties.width (number : null : IRW)
//
// Specifies the width of the image.
//
// @visibility external
//<

//> @attr imgProperties.height (number : null : IRW)
//
// Specifies the height of the image.
//
// @visibility external
//<

//> @attr imgProperties.name (string : null : IRW)
//
// Specifies the name of the image. This is an identifier unique to the canvas, and subsequent
// calls to <code>+link{method:canvas.getImage()}</code> and
// <code>+link{method:canvas.setImage()}</code>
// with this name will act on the image object created using this ImgProperties object.
//
// @visibility external
//<

//> @attr imgProperties.extraStuff (string : null : IRW)
//
// Specifies the additional attributes to write in the tag.
//
// @visibility external
//<

//> @attr imgProperties.imgDir (URL : null : IRW)
//
// Specifies the image-specific image directory to override the default.
//
// @visibility external
//<

//>	@method canvas.imgHTML() (A)
// Generates the HTML for an image unique to this Canvas.
// <P>
// The full URL for the image will be formed according to the rules documented for
// <code>+link{method:canvas.getImgURL()}</code>.
// <P>
// The created image will have an identifier unique to this Canvas, and subsequent calls to
// <code>+link{method:canvas.getImage()}</code> and
// <code>+link{method:canvas.setImage()}</code>
// with the name passed to this function will act on the image object produced by the HTML
// returned from this call.
//
// @param src           (SCImgURL)	URL local to the skin or application directory.<br>
//		NOTE: instead of passing several parameters, you can pass an object as the 'src'
//      parameter with properties for all the various function parameters with, eg:<br>
//      canvas.imgHTML( {src:"foo", width:10, height:10} );
//
// @param [width]       (number)	width of the image
// @param [height]      (number)	height of the image
// @param [name]        (string)	name for the image
// @param [extraStuff]  (string)	additional attributes to write in the tag
// @param [imgDir]      (string)	image-specific image directory to override the default
//                                  for this Canvas
// @return	(string)				HTML to draw the image.
//
// @group images
// @visibility external
//<
// @param [generateSpan] (boolean)  whether to generate the HTML for a &lt;span&gt; element instead
//                                  of an &lt;img&gt; element.
imgHTML : function (src, width, height, name, extraStuff, imgDir, activeAreaHTML, generateSpan) {
    return isc.Canvas.imgHTML(src, width, height, name, extraStuff, imgDir, activeAreaHTML, this, false, generateSpan);
},

// returns an imgHTML template that contains an open slot for a unique name attribute
// for the image.  Used in inner loops.
_getImgHTMLTemplate : function (src, width, height, name, extraStuff, imgDir, activeAreaHTML, generateSpan) {
    return isc.Canvas.imgHTML(src, width, height, name, extraStuff, imgDir, activeAreaHTML, this, true, generateSpan);
},

//>	@method	canvas.getImage() (A)
// Retrieve a native image element by name.
// <P>
// The image element must have been created from HTML generated by calling
// +link{Canvas.imgHTML()} on this particular Canvas.
//
// @param	identifier (string)	name of the image to get, as originally passed to
//                              <code>imgHTML()</code>
// @return	(DOMElement)	DOM image element if found, else null
// @group images
// @visibility external
//<
getImage : function (identifier, checkSpans) {
	// if identifier is a string, prepend the canvas name
	if (isc.isA.String(identifier)) identifier = this.getCanvasName() + identifier;

	// if we've been drawn and there is a document object to query
    var handle = this.getHandle();
	if (handle) {
        var doc = handle.document || document;
        if (isc.Page.isXHTML() || checkSpans) {
            
            return doc.getElementById(identifier);
        } else {
            // just ask for the image
            
            return doc.images[identifier];
        }
    }

	// not found -- return null;
	return null;
},


//>	@method	canvas.setImage() (A)
// Set the URL of an image element by name.
// <p>
// The image element must have been created from HTML generated by calling
// <code>canvas.imgHTML()</code> on this particular Canvas.
//
// @param identifier (string)	name of the image to change, as originally passed to
//                              <code>imgHTML</code>
// @param URL		 (SCImgURL)	URL for the image
// @param [imgDir]	 (string)	optional image directory, overrides the default for this Canvas
// @group images
// @visibility external
//<
setImage : function (identifier, src, imgDir, checkSpans) {
	// get the image
    var image = this.getImage(identifier, checkSpans);

    if (image == null) {
        //>DEBUG
        this.logWarn("setImage: image '" + identifier + "' couldn't be found");
        //<DEBUG
        return;
    }

    isc.Canvas._setImageURL(image, src, imgDir, this);
},

//> @method canvas.linkHTML() (A)
// Generates the HTML for a standard link (anchor) element.
//
// @param href  (string)	URL for the link to point to
// @param [text] (HTMLString) HTML to display in the link element (defaults to the href)
// @param [target] (string)   Target window for the link (defaults to opening in a new, unnamed window)
// @param [ID] (string)     optional ID for the link element to be written out
// @param [tabIndex] (Integer) optional tabIndex for the link
// @param [accessKey] (string) optional accessKey for the link
// @return (HTMLString) HTML for the link
// @visibility external
//<
// Additional 'extrastuff' param
linkHTML : function (href, text, target, ID, tabIndex, accessKey, extraStuff) {
    return isc.Canvas.linkHTML(href,text,target, ID, tabIndex, accessKey, extraStuff);
},

// ----------------------------------------------------------------------------------------

//>	@method	canvas.inWhichPosition()	(A)
//			given a list of numerical coordinates and a single coordinate,
//			return which item the coordinate falls in
//
//		@group	utils
//
//		@param	list			(array of numbers)	these are sizes (widths, heights, etc) of each
//		                         item, such as that returned by Canvas.applyStretchResizePolicy()
//		@param	coord			(number)	coordinate, such as an x or y coordinate from an Event
//		@param	[direction]	    (Page.LTR or Page.RTL)	direction
//									-- if LTR we scan from left to right, if RTL we scan from right
//									   to left unspecified == LTR
//		@return	(number)
//				-1 = before beginning of list
//				-2 = after end of list
//				 #  = in that position
//<
inWhichPosition : function (list, coord, textDirection) {
    //this.logWarn("inWhichPosition: coord: " + coord + "\nlist: " + list);

    // if we're before the first item, return -1
    if (!list || coord < 0) return -1;

    // iterate through the list of sizes, returning the one containing the coord
    if (textDirection == isc.Page.RTL) {
        // direction right to left: coord is still an offset from left, but assume list of
        // lengths is laid out right to left
        var totalSize = list.sum();
        for (var c = 0, numCols = list.length; c < numCols; c++) {
            if (coord >= totalSize-list[c]) return c;
            totalSize -= list[c];
        }
    } else {
        for (var c = 0, numCols = list.length; c < numCols; c++) {
            if (coord <= list[c]) {
                return c;
            }
            coord -= list[c];
        }
    }
    // not found -- return -2
    return -2;
},

// Log window: update stats
// --------------------------------------------------------------------------------------------

// add or remove from canvasList
_$count : "count",
_canvasList : function (add) {
    var list = isc.Canvas._canvasList;
    if (add) list.add(this);
    else list.remove(this);
    //>DEBUG
    if (this._iscInternal) {
        isc.Canvas._iscInternalCount += (add ? 1 : -1);
    } else {
        isc.Log.updateStats(this._$count);
    } //<DEBUG
},

//>DEBUG increment some stat we're drawing an update the log window
_addStat : function (stat) {
    if (!this._iscInternal) {
        isc.Canvas._stats[stat]++;
        isc.Log.updateStats(stat);
    }
}, //<DEBUG


// Attached Peers
// ----------------------------------------------------------------------------------------


_attachedPeers : function (side) {
    var attachedPeers = this._attachedPeerMap;
    if (!attachedPeers) return null;
    if (side) return attachedPeers[side];
},

_registerAttachedPeer : function (peer, side, offset, observeResize) {
    if (peer == null || side == null) return;
    if (!this._attachedPeerMap) this._attachedPeerMap = {};
    if (!this._attachedPeerMap[side]) this._attachedPeerMap[side] = [];
    this._attachedPeerMap[side].add(peer);
    
    if (offset != null) peer._attachedPeerOffset = offset
    // observeResize param -- most "attachedPeers" are statically sized, like edges, so avoid this
    // unless explicitly requested, but if the peer could change size we'll need to refresh our
    // margins (and hence resize our handle) to accomodate it when it does.
    if (observeResize) {
        this.observe(peer, "resized", "observer._attachedPeerResized(observed)");
    }
    // Note: on destroy, we destroy all peers so no need to clean up this list.
    // We also have a sanity check for the peer being destroyed when calculating margins.

    delete this._cachedMargins;
    delete this._fullMargins;
},

_unRegisterAttachedPeer : function (peer, side, offset) {
    if (peer == null || side == null) return;
    if (!this._attachedPeerMap ||!this._attachedPeerMap[side]) return;
    this._attachedPeerMap[side].remove(peer);
    if (this.isObserving(peer, "resized")) {
        this.ignore(peer, "resized");
    }
    delete peer._attachedPeerOffset;
    delete this._cachedMargins;
    delete this._fullMargins;
},

_attachedPeerResized : function (peer) {
    // This will clear the cached margins which were calculated based on attached peer sizes
    // and refresh on the style-handle
    this.refreshMargin();
},

refreshMargin : function () {
    this.setMargin(this.margin);
},

// -----------------------------------------------------------------------------------------





//>CornerClips
// make corner cap elements for subtractive rounded corners
_makeCornerClips : function () {
	this._cornerClips = {};
	for (var i = 0; i < this.clippedCorners.length; i++) {
		this._makeCornerClip(this.clippedCorners[i])
	}
},

// make a single corner cap element
// could convert position param to a cap properties obj for more flexibility here
// (if we do, note that helper methods currently assume position is a string)
// should check for valid positions here
_makeCornerClip : function (position) {
	var clips = this._cornerClips,
		capLeft = this.left,
		capTop = this.top,
		capWidth = this.cornerClipWidth || this.cornerClipSize,
		capHeight = this.cornerClipHeight || this.cornerClipSize;

	// calculate position for this corner
	if (position == "TR" || position == "BR") {
		capLeft = capLeft + this.getWidth() - capWidth;
	}
	if (position == "BL" || position == "BR") {
		capTop = capTop + this.getHeight() - capHeight;
	}

    // we can only do no-image corner masks in IE5.5+
    if (this.noCornerClipImages && !(isc.Browser.isIE && isc.Browser.minorVersion >= 5.5)) {
        this.noCornerClipImages = false;
    }

	var newCap = clips[position] = isc.ClassFactory.newInstance({
		_constructor:(this.noCornerClipImages ? "Canvas" : "Img"),
		left:capLeft,
		top:capTop,
		width:capWidth,
		height:capHeight,
		eventProxy:this,
        //border:"1px solid blue",
		src:(this.noCornerClipImages ? null : this._getCornerImage(position)),
        
		contents:(this.noCornerClipImages ?
            this._getCornerHTML(capWidth, capHeight, position) : null)

	}, this._cornerProperties);

	this.addPeer(newCap);
	newCap.moveAbove(this);
},

_finishCornerClips : function () {
    if (!this.noCornerClipImages) return;
    for (var edge in this._cornerClips) {
        var cap = this._cornerClips[edge],
            div = cap.getHandle().firstChild,
            style = div.style;
        //this.logWarn("edge: " + edge + ", cap div: " + this.echo(div));

        // kick in irising transition
        div.filters[0].apply();
        // transition to hidden version (to create transparent space)
        style.visibility = "hidden";
        // move transition to 71% completion (where circle touches edge)
        div.filters[0].percent=71;
    }
},

// generate image filename
// jumping directly to Img.urlForState() here; is there a better approach?
// currently using position and color only; could enhance with size if scaling is unacceptable
_getCornerImage : function (position) {
	return isc.Img.urlForState(
		this.cornerClipImage,
		null, // selected is unused
        null, // as is focused
		this.cornerClipColor, // embed hex color code in the file name
		position);
},

_getCornerHTML : function (capWidth, capHeight, edge) {

    var output = isc.SB.create();

    

    // containing DIV: 4x the area of the final cap, since we want to show only 1/4 of frozen
    // transition per cap
    output.append(
        "<DIV STYLE='width:", 2*capWidth,
                 "px;height:", 2*capHeight,
                 "px;filter:progid:DXImageTransform.Microsoft.iris(irisStyle=circle,motion=out);"
                 //,"border:1px solid red;"
    );
    // move left/top so that only relevant corner shows
    if (edge.contains("R")) output.append("margin-left:", -capWidth, "px;");
    if (edge.contains("B")) output.append("margin-top:", -capHeight, "px;");

    // NOTE: needs overflow:hidden or it will expand to one line-height
    output.append("'><DIV STYLE='overflow:hidden;width:", capWidth, "px;height:", capHeight,
                  "px;background-color:", this.cornerClipColor, ";");
                  //"px;background-color:orange;");

    // move right/bottom to place in relevant corner of 4x area container
    if (edge.contains("R")) output.append("margin-left:", capWidth, "px;");
    if (edge.contains("B")) output.append("margin-top:", capHeight, "px;");
    output.append("'></DIV></DIV>");

    //this.logWarn(this.getCallTrace() + ", html: " + output.toString());

    return output.toString();

    

},




//<CornerClips

//>RoundCorners




_edgesAsPeer : function () {
    return this.showEdges && !this.edgesAsChild;
},

_createEdges : function () {
    if (!this.showEdges || isc.isA.EdgedCanvas(this) || this._edgedCanvas != null) {
        return this._edgedCanvas;
    }
    var edgedCanvas = this._edgedCanvas = this._createEdgedCanvas();

    if (this.edgesAsChild) {
        edgedCanvas.resizeTo("100%", "100%");
        edgedCanvas.sendToBack();
        this.addChild(edgedCanvas);
    } else {
        this.addPeer(edgedCanvas);
    }
    return edgedCanvas;
},

//> @attr canvas.showEdges (Boolean : false : IR)
// Whether an +link{class:EdgedCanvas} should be used to show image-based edges around this
// component.
//
// @group imageEdges
// @visibility roundCorners
// @example edges
//<

//> @attr canvas.edgeSize (number : 10 : IR)
// @include edgedCanvas.edgeSize
// @example edges
//<

//> @attr canvas.edgeOffset (number : null : IR)
// @include edgedCanvas.edgeOffset
// @example corners
//<

//> @attr canvas.edgeImage (SCImgURL : "[SKIN]edge.gif" : IR)
// @include edgedCanvas.edgeImage
// @example edges
//<

//> @attr canvas.customEdges (Array of String : null : IR)
// @include edgedCanvas.customEdges
//<

//> @attr canvas.edgeBackgroundColor (Color: null : IR)
// Background color for the EdgedCanvas created to decorate this component.  This can be used
// to provide an underlying "tint" color for translucent edge media
//
// @group imageEdges
// @visibility roundCorners
//<

//> @attr canvas.edgeOpacity (int : null : IR)
// Opacity of the edges.  Defaults to matching this.opacity.
// if +link{Canvas.setOpacity()} is called on a Canvas where edgeOpacity is set,
// edgeOpacity will be considered a percentage of the parent's opacity (so 50% opaque parent plus
// edgeOpacity 50 means 25% opaque edges)
// @setter setEdgeOpacity()
//
// @group imageEdges
// @visibility external
// @example edges
//<

//> @attr canvas.edgeShowCenter (Boolean : false : IR)
// @include edgedCanvas.showCenter
// @example corners
//<

//> @attr canvas.edgeCenterBackgroundColor (Color: null : IR)
// @include edgedCanvas.centerBackgroundColor
//<

//>	@method	canvas.setEdgeOpacity()
// Set the +link{edgeOpacity} and mark the canvas for redraw
// @param	newOpacity	(number)	new edge-opacity level
// @visibility external
// @example edges
//<
setEdgeOpacity : function (newOpacity) {
    var realOpacity = this.edgeOpacity = newOpacity;
    if (this.opacity > 0 && this.opacity < 100) {
        realOpacity = this.opacity * (this.edgeOpacity / 100);
    }
    this._edgedCanvas.setOpacity(realOpacity);
},


_edgePassThroughs : [
    "edgeImage", "edgeColor", "customEdges", "shownEdges",
    "edgeSize", "edgeTop", "edgeBottom", "edgeLeft", "edgeRight",
    "edgeOffset", "edgeOffsetTop", "edgeOffsetBottom", "edgeOffsetLeft", "edgeOffsetRight",
    "canDragResize", "canDragReposition"
],
_createEdgedCanvas : function () {
    // pass through edge-related properties
    var propNames = this._edgePassThroughs,
        edgedCanvas = isc.EdgedCanvas.createRaw();
    edgedCanvas.autoDraw = false;
    edgedCanvas._generated = true;
    edgedCanvas.containedPeer = true;
    edgedCanvas.dragTarget = this;

    edgedCanvas.visibility = this.visibility;
    edgedCanvas.opacity = this.opacity;
    edgedCanvas.useOpacityFilter = this.useOpacityFilter;
    if (this.edgeOpacity != null) {
        edgedCanvas.opacity = this.edgeOpacity;
        edgedCanvas._setOpacityWithMaster = false;
    }
    edgedCanvas.smoothFade = this.smoothFade;

    
    if (this.edgeOverflow != null) edgedCanvas.overflow = this.edgeOverflow;

    // edged canvases behavior like super-borders, so have the thing they're
    // attached to handle events occurring on the edge (as it would with standard CSS borders)
    edgedCanvas.eventProxy = this;

    for (var i = 0; i < propNames.length; i++) {
        var name = propNames[i];
        if (this[name] != null) edgedCanvas[name] = this[name];
    }
    if (this.edgeBackgroundColor) edgedCanvas.backgroundColor = this.edgeBackgroundColor;
    if (this.edgeCenterBackgroundColor) {
        edgedCanvas.centerBackgroundColor = this.edgeCenterBackgroundColor;
    }
    if (this.edgeShowCenter != null) edgedCanvas.showCenter = this.edgeShowCenter;
    if (!this.edgesAsChild) edgedCanvas.zIndex = this.getZIndex(true)-1;

    edgedCanvas.completeCreation();
    return edgedCanvas;
},


// NOTEs on shadow placement and softness
// "thrower" means the element that throws the shadow.
//
// Possible properties for configuring shadows:
//
// - offset
//   How far the shadow is offset from the thrower
//   Physically:
//   - with a fixed light source, offset increases with thrower height
//   - a more angular light source causes larger offsets for all throwers, and a more distant
//     light source causes less offset
//   - technically, viewer angle would also change offset, but we assume the viewer is centered
//
// - softness
//   How much blurring there is along the shadows edges, and how much larger the shadow is than
//   the element that throws it.
//   Physically:
//   - with a fixed light source, softness increases with thrower height
//   - a light source closer to the page causes larger, softer shadows for all throwers
//
// - angle
//   Direction shadow is offset.  This is almost always 45 degrees down/right, and we don't
//   support altering this
//
// - depth
//   A combination of softness and offset.
//   Physically, given a single light source and thrower height, both softness and
//   offset are known.  For a given depth, we implement an arbitrary default relationship
//   between depth and softness (implying a particular light source distance) and between depth
//   and offset (implying a particular light source distance and angle)
//
// Sample softness/offset relations known to look nice:
//
// softness    offset    shadow pixels visible left/above   shadow pixels visible right/below
// 1           1         0                                  2
// 2           1         1                                  3
// 3           2         1                                  5
// 6           2         4                                  8

//> @attr canvas.showShadow     (Boolean : false : [IRW])
// Whether to show a drop shadow for this Canvas
//
// @setter canvas.setShowShadow()
// @group shadow
// @example shadows
// @visibility external
//<

//> @attr canvas.shadowDepth    (number : 4 : [IR])
// Depth of the shadow, or the virtual height above the page of the widget throwing the shadow.
// <P>
// This is a single parameter that can be used to control both <code>shadowSoftness</code> and
// <code>shadowOffset</code>.
//
// @visibility external
// @group shadow
//<
shadowDepth: 4,

//> @attr canvas.shadowOffset   (number : null : IRWA)
// Offset of the shadow.  Defaults to half of <code>shadowDepth</code> if unset.
// <P>
// Because of the blurred edges, a shadow is larger than the originating component by
// 2xsoftness.  An <code>shadowOffset</code> of 0 means that the shadow will extend around the
// originating component equally in all directions.
//
// @visibility external
// @group shadow
// @example shadows
//<

//> @attr canvas.shadowSoftness (number : null : IRWA)
// Softness, or degree of blurring, of the shadow.
// <P>
// A shadow with <code>softness:x</code> is 2x pixels larger in each direction than the element
// throwing the shadow, and the media for each edge should be x pixels wide/tall.
// <P>
// Defaults to <code>shadowDepth</code> if unset.
//
// @visibility external
// @group shadow
// @example shadows
//<

//> @attr canvas.shadowImage   (SCImgURL : "[SKIN]ds.png" : [IRA])
// Base name of the series of images for the sides, corners, and center of the shadow.
// <P>
// The actual image names fetched for the dropShadow combine the segment name and the
// <code>shadowDepth</code> setting.  For example, given "ds.png" as the base name, a depth of
// 4, and the top-left segment of the shadow, we'd use "ds4_TL.png".
// <P>
// The names for segments are the same as those given for controlling resizable edges; see
// +link{attr:canvas.resizeFrom}.  The center segment has the name "center".  The center segment is
// the only segment that doesn't include the depth in the URL, so the final image name for the
// center given a baseName of "ds.png" would be just "ds_center.png".
//
// @visibility external
// @group shadow
//<

//> @method canvas.setShowShadow()
// Method to update +link{canvas.showShadow}.
// @param showShadow (boolean) true if the shadow should be visible false if not
// @visibility external
// @group shadow
//<
setShowShadow : function (showShadow) {
    this.showShadow = showShadow;
    if (showShadow) {
        if (!this._shadow) this._createShadow();
        else if (this.isDrawn()) this._shadow.show();
    } else {
        if (this._shadow) this._shadow.hide();
    }
},

_createShadow : function () {
    var shadow = this._shadow = this.createAutoChild("shadow",
                                                     {visibility:this.visibility,
                                                      zIndex:this.getZIndex(true)-3},
                                                     isc.DropShadow);
    this.updateShadow(true);

    this.addPeer(shadow);
    shadow.moveBelow(this);
},

// whether to allow drag resizing from the shadow.  Generally useful as the shadow, if present,
// occludes other elements and is hence a dead zone in terms of interactivity without this
// behavior.

dragResizeFromShadow:true,

updateShadow : function (initTime) {
    if (!initTime) this.setShowShadow(this.showShadow);
    var shadow = this._shadow;
    if (!shadow) return;

    shadow.offset = this.shadowOffset;
    shadow.offsetX = this.shadowOffsetX;
    shadow.offsetY = this.shadowOffsetY;
    shadow.softness = this.shadowSoftness;

    if (this.shadowImage) shadow.setEdgeImage(this.shadowImage);

    // NOTE: setDepth recalculates offsets and softness, even if depth change is a no-op
    shadow.setDepth(this.shadowDepth);

    if (this.dragResizeFromShadow && this.canDragResize) {
        // NOTE: master's setting for canDragResize is dynamically checked via overrides on the
        // DropShadow class
        shadow.canDragResize = this.canDragResize;
        shadow.resizeFrom = this.resizeFrom;
        shadow.dragTarget = this;
    }
},
//<RoundCorners

_$shadow:"shadow",
propertyChanged : function (propName, value) {
    if (isc.contains(propName, this._$shadow) && this.updateShadow) this.updateShadow();
},


// Group Frame APIs
// ---------------------------------------------------------------------------------------

//> @attr canvas.isGroup (boolean : false : IR)
// Should a grouping frame be shown around this canvas.
// @see canvas.groupBorderCSS
// @see canvas.groupLabelStyleName
// @see canvas.groupLabelBackgroundColor
// @visibility external
//<
// isGroup - should we show a grouping frame around this canvas?

isGroup:false,

setIsGroup : function (isGroup) {
    if (isGroup == this.isGroup) return;
    // horrible hack: we use registerAttachedPeer to account for the space taken up
    // by the label with margins - for whatever reason this doesn't update on a drawn widget
    // without a clear/draw(), so explicitly clear / draw if necessary
    var mustClear = this.shouldShowGroupLabel() && this.isDrawn();
    if (mustClear) this.clear();
    if (isGroup) {
        this._standardBorder = this.border;
        this.setBorder(this.groupBorderCSS);
        if (this.shouldShowGroupLabel()) this._showGroupLabel();
    } else {
        this.setBorder(this._standardBorder || "");
        if (this.shouldShowGroupLabel()) this._hideGroupLabel();
    }
    this.isGroup = isGroup;
    if (mustClear) this.draw();
},


//> @attr canvas.groupBorderCSS (String : "2px solid black" : IR)
// Sets the style for the grouping frame around the canvas. Only necessary when +link{canvas.isGroup}
// is set to true.
// @group appearance
// @visibility external
//<
groupBorderCSS:"2px solid black",

groupLabelPadding:10,

showGroupLabel:true,
shouldShowGroupLabel : function () {
    return this.showGroupLabel;
},

//> @attr canvas.groupLabelStyleName (CSSStyleName : "groupLabel" : IR)
// Sets the style for the grouping label. Only necessary when +link{canvas.isGroup}
// is set to true.
// <p>
// Note that +link{groupLabelBackgroundColor} overrides any background-color of this style.
// @group appearance
// @visibility external
//<
groupLabelStyleName:"groupLabel",

//> @attr canvas.groupLabelBackgroundColor (CSSColor : null : IRW)
// If set, the background color of the grouping label. Only applicable when +link{isGroup}
// is set to true.
// <p>
// This corresponds to the CSS background-color property on the grouping label. You can set this
// property to an RGB value (e.g. #22AAFF) or a named color (e.g. red) from a list of browser supported
// color names.
// <var class="smartgwt"><p>
// The getter for this attribute, {@link #getGroupLabelBackgroundColor()}, returns the color
// that will actually be used; i.e. if groupLabelBackgroundColor is left unset or is set to
// null, then getGroupLabelBackgroundColor() returns the color string that will be used.
// </var>
// @setter Canvas.setGroupLabelBackgroundColor()
// @group appearance
// @visibility external
//<
//groupLabelBackgroundColor: null,

// creates the groupLabel canvas (and sets default properties)
makeGroupLabel : function () {

    if (!this.groupLabel) {
        var dynamicDefaults = {
            autoDraw:false,
            backgroundColor:this.getGroupLabelBackgroundColor(),
            eventProxy:this,
            styleName:this.groupLabelStyleName
        }
        if (this.groupTitle != null) dynamicDefaults.contents = this.groupTitle;
        this.groupLabel = this.createAutoChild("groupLabel", dynamicDefaults);

    } else {
        if (this.groupTitle != null) this.groupLabel.setContents(this.groupTitle);
        this.groupLabel.setBackgroundColor(this.getGroupLabelBackgroundColor());
    }
},

// default groupLabel background color to match us, or be white if we're transparent.
// We need to return a color here to mask the grouping frame.
getGroupLabelBackgroundColor : function () {
    if (this.groupLabelBackgroundColor) return this.groupLabelBackgroundColor;
    if (this.backgroundColor) return this.backgroundColor;
    // should check this.styleName.backgroundColor too...
    return "white";
},

//> @method canvas.setGroupLabelBackgroundColor()
// Setter for +link{groupLabelBackgroundColor}.
// @param groupLabelBackgroundColor (CSSColor) the new grouping label background color.
// @visibility external
//<
setGroupLabelBackgroundColor : function (groupLabelBackgroundColor) {
    this.groupLabelBackgroundColor = groupLabelBackgroundColor;
    if (this.groupLabel) this.groupLabel.setBackgroundColor(this.getGroupLabelBackgroundColor());
},

_showGroupLabel : function () {
    this.makeGroupLabel();

    var label = this.groupLabel;
    // draw the groupLabel offscreen so we can pick up vertical sizing
    var labelHeight;
    if (label.overflow == isc.Canvas.VISIBLE) {
        // Ensure it's top level before trying to draw it offscreen
        if (label.parentElement != null) label.deparent();
        label.setTop(-1000);
        label.draw();
        labelHeight = label.getVisibleHeight();

    } else {
        labelHeight = label.getVisibleHeight();
    }
    // the groupLabel will be an attached peer - this handles updating the top margin
    var offset = Math.round(labelHeight / 2);
    this._registerAttachedPeer(label, isc.Canvas.TOP, offset);

    // very crude- explicitly set topPadding to ensure there's enough space
    
    var padding = labelHeight - offset;
    if (this.padding) padding += this.padding;
    this.setTopPadding(padding);

    this._moveGroupLabelIntoPlace();

    if (label.masterElement != this) this.addPeer(label);
    if (this.isDrawn()) {
        if (!label.isDrawn()) label.draw();
    }
    // xxx hack: run calculate margins to ensure the visible height of the label is remembered
    // as top margin (it will likely be cleared before being added to layouts etc)
    this.getTopMargin();
    label.moveAbove(this);
    // if we had to draw the label offscreen, and we're not yet drawn, clear now.
    if (label.isDrawn() && !this.isDrawn()) label.clear();
},
_hideGroupLabel : function () {
    if (!this.groupLabel) return;
    var label = this.groupLabel;
    this._unRegisterAttachedPeer(label, isc.Canvas.TOP);
    this.setTopPadding(null);
    label.clear();
    // xxx this depeer is required to ensure it doesn't draw with us next time we get drawn!
    label.depeer();
},
_moveGroupLabelIntoPlace : function () {
    var label = this.groupLabel;

    // can update this to support center / right alignment fairly easily
    var left,
        top = this.getTop();
    if (this.isRTL()) {
        left = this.getRight() - this.groupLabelPadding - label.getVisibleWidth();
    } else {
        left = this.getLeft() + this.groupLabelPadding;
    }
    label.moveTo(left, top);
},

//groupLabelConstructor:"Label"
groupLabelDefaults:{
    // default to a Label
    _constructor:"Label",
    // which fits its content
    overflow:"visible",
    height:1, width:1,
    _resizeWithMaster:false,
    wrap:false,
    // center in both directions
    vAlign:"center", align:"center",

    redraw : function () {
        var ret = this.Super("redraw", arguments);
        this.creator._moveGroupLabelIntoPlace();
        return ret;
    },
    handleParentMoved : function () {
        this.Super("handleParentMoved", arguments);
        this.creator._moveGroupLabelIntoPlace();
    }
},

//> @attr canvas.groupTitle (HTMLString : null : IRW)
// The title/label for the grouping. Only applicable when +link{Canvas.isGroup,isGroup} is set to true.
// @setter Canvas.setGroupTitle()
// @group appearance
// @visibility external
//<

//> @method canvas.setGroupTitle()
// Setter for +link{Canvas.groupTitle}.
// @param newTitle (HTMLString) The new title for the grouping.
// @group appearance
// @visibility external
//<
setGroupTitle : function (newTitle) {
    this.groupTitle = newTitle;
    if (this.groupLabel) {
        this.groupLabel.setContents(this.groupTitle);
    } else {
        this._showGroupLabel();
    }
}

});



isc.Canvas.addClassMethods({



// for canvas start/end in useCaptureSpan mode.
//
// Note: we need to strip ALL script tags - i.e. not just the Javascript ones so that
// e.g. VBScript doesn't re-execute. That's why we don't reuse code from HTMLFlow here.
//
// Note: these will incorrectly strip matching text in e.g. strings and textareas - but that
// should be a somewhat unlikely occurrence.
//
// Furthermore, we can provide these as semi-public override points for easy patching if anyone
// runs into a problem.
stripScriptTags : function (html) {
    // \r required for Firefox 1.0 (and probably earlier moz) - otherwise doesn't match EOL
    return html.replace(/<script([^>]*)?>(.|\n|\r)*?<\/script>/ig, isc.emptyString);
},
stripLinkTags : function (html) {
    return html.replace(/<link([^>]*)?>/ig, isc.emptyString);
},


// DOM emulation
// --------------------------------------------------------------------------------------------

//> @classMethod Canvas.getById()
// Retrieve a Canvas by it's global +link{canvas.ID,ID}.
// @param ID (String) global ID of the Canvas
// @return (Canvas) the Canvas, or null if not found
// @visibility external
//<
getById : function (sId) {
    var canvas = window[sId] || null;
    return canvas ? (isc.isA.Canvas(canvas) ? canvas : null) : null;
},

// get the next zIndex for the next item to be drawn. see setZIndex() for notes
getNextZIndex : function () {
    return (isc.Canvas._nextZIndex += 18);
},

// getFocusProxyString()
// This will return HTML for a natively-focusable element with no visual representation to be
// written into the DOM.

getFocusProxyString : function (tagBaseName, absolute, offsetLeft, offsetTop, width, height, isVisible,
                                canFocus, tabIndex, accessKey, eventsHandledNatively,
                                focusHandler, blurHandler,
                                keyDownHandler, keyPressHandler, keyUpHandler)
{
    if (this._focusProxyTemplate == null) {
        this._onfocus = "' ONFOCUS=";
        this._closeQuoteSpace = "' ";
        this._onblur = " ONBLUR=";
        this._focusProxyTemplate = [
            "<div",                                                     // 0
            " id='",                                                     // 1
                null,                                                   // 2 - tagBaseName
            "__focusProxyParent'" +
            " style='overflow:hidden;width:0px;height:0px;position:",   // 3
                ,                                                       // 4 - position (absolute/inline)
                ";left:",                                               // 5
                    null,                                               // 6 - offsetLeft
                "px;top:",                                              // 7
                    null,                                               // 8 - offsetTop
                "px;'>",                                                // 9

            
            (isc.Browser.isSafari ?
                "<textarea" :
                (isc.Browser.isMoz && isc.Browser.geckoVersion >= 20051111 ?
                    "<div" :
                    
                    "<button onclick='event.cancelBubble=true;return false;'"
                )
            ),                                                          // 10

                " id='",                                                 // 11
                    null,                                               // 12 - tagBaseName
                "__focusProxy'",                                         // 13

                // Note: if we are not visible, draw the focusProxy as not being visible either
                // - this prevents it from being focus-able, until we get shown
                " style='VISIBILITY:",                                  // 14
                    null,                                               // 15 - visible / hidden

                // the proxy button is drawn inside it's 0x0 parent div (with overflow hidden)
                // Note - if you make parentDiv bigger than 0x0, and position the focus proxy
                // such that it's initially not visible, when it receives focus, the parent
                // div scrolls, making it visible (at least in Moz)
                // The 0x0 div is supported in both Moz and Safari, so use this to ensure it's
                // not visible
                "left:1px;top:1px;" +

                
                "width:",                                               // 16
                    (isc.Browser.isSafari ? "1" : null),                // 17 - width
                "px;height:",                                           // 18
                    (isc.Browser.isSafari ? "1" : null),                // 19 - height
                "px;",                                                  // 20
                    null,                                               // 21 - -moz-user-focus (Moz Only)
                this._onfocus,                                           // 22
                    null,                                               // 23 - focusHandler
                this._onblur,                                             // 24
                    null,                                               // 25 - blurHandler
                null, null, null,                                       // 26-28 - onKeyDown/up/press
                " tabindex='",                                           // 29
                    null,                                               // 30 - TabIndex
                    null,                                               // 31 - "ACCESSKEY=x"

                // Hang this attribute on the tag so we can recognize it's not a native form
                // item in EH.eventHandledNatively
                "' focusProxy='true' handleNativeEvents='",                 // 32
                    null,                                               // 33 - true / false
                "'>",
//                tagBaseName," focus proxy" +
                (isc.Browser.isSafari ? "</textarea>" :
                    (isc.Browser.isMoz && isc.Browser.geckoVersion >= 20051111 ? "</div>"
                                                                               : "</button>")
                ),
                "</div>"
        ]
    }

    var template = this._focusProxyTemplate;

    template[2] = tagBaseName;
    template[4] = (absolute ? "absolute" : "inline");
    template[6] = offsetLeft;
    template[8] = offsetTop;
    template[12] = tagBaseName;
    template[15] = (isVisible ? "visible;" : "hidden;");
    template[17] = width;
    template[19] = height;
    if (isc.Browser.isMoz) {
        if (!canFocus || tabIndex == -1) template[21] = "-moz-user-focus:ignore;";
        else template[21] = "-moz-user-focus:normal;"
    }
    if (focusHandler && focusHandler != isc.emptyString) {
        template[22] = this._onfocus;
        template[23] = focusHandler;
    } else {
        template[22] = this._closeQuoteSpace; // close the style=' attribute
        template[23]= null
    }
    if (blurHandler && blurHandler != isc.emptyString) {
        template[24] = this._onblur;
        template[25] = blurHandler
    } else {
        template[24] = null;
        template[25] = null;
    }

    // Only write key handlers in if they were passed in
    
    template[26] = (keyDownHandler != null ? " onkeydown=" + keyDownHandler : null);
    template[27] = (keyPressHandler != null ? " onkeypress=" + keyPressHandler : null);
    template[28] = (keyUpHandler != null ? " onkeyup=" + keyUpHandler : null);

    template[30] = (canFocus ? tabIndex : -1);
    template[31] = (canFocus && accessKey ? "' accesskey='" + accessKey : null);

    template[33] = (eventsHandledNatively ? true : false);

    return template.join(isc._emptyString);

},

// TabIndex auto-allocation
// Helper to look at our complete allocated tab chain.
showAllocatedTabChain : function () {
    var firstTabWidget = isc.EH._firstTabWidget,
        lastTabWidget = isc.EH._lastTabWidget;
    var info = "First tab widget:" + firstTabWidget +  ", and last:" + lastTabWidget +
         "\nFull chain:";
    var currentWidget = firstTabWidget;
    do {
        info += "\n\t" + currentWidget.getID() + " - " + currentWidget.getTabIndex() + " -->";
        currentWidget = currentWidget._nextTabWidget;
    } while (currentWidget != null && currentWidget != lastTabWidget)

    this.logWarn(info);
},



// CSS Caching
// ---------------------------------------------------------------------------------------

// wipe out any cached CSS information.  This is needed for
// - Safari 2.0 and earlier where we get bad info before page load
// - automated tests that load stylesheets
// - possible future advanced usage like on the fly skin change
clearCSSCaches : function () {

    // tell the Element class to clear all generic CSS caches
    isc.Element._clearCSSCaches();

    // wipe out cached style information on Canvases
    var list = isc.Canvas._canvasList;
    for (var i = 0; i < list.length; i++) {
        var canvas = list[i];
        if (canvas == null || canvas.destroyed) continue;
        canvas._fullMargins = canvas._cachedMargins =
            canvas._cachedBorderSize = canvas._cachedPadding = null;
    }
},


// Image locations and skinning
// --------------------------------------------------------------------------------------------

//>!BackCompat 2005.2.23
// Removing these setters and getters, as switching images to a different directory on the
// fly is a pointless feature, and getters don't seem necessary.

//>	@classMethod Canvas.setAppImgDir()
// Set the default app-specific image directory for all canvases of this type.
// <p>
// Note: this will not cause any instances to redraw, but having them
// redraw will show the new images.
//
// @param URL		(string)	New URL for the app-specific images.
// @group images
//<
setAppImgDir : function (URL) {
	this.getPrototype().appImgDir = URL;
},

//>	@classMethod Canvas.getAppImgDir()
// Return the image directory for this class of widgets, prepended with the Page image
// directory.
//
// @group images
// @return (URL)	Image directory (including Page image directory) for this widget.
//<
getAppImgDir : function () {
	return isc.Page.getImgURL(isc.emptyString, this.getPrototype().appImgDir);
},


//>	@classMethod Canvas.setSkinImgDir()
// Set the default widget image directory for all canvases of this type.
// <p>
// Note: this will not cause any instances to redraw, but having them
// redraw will show the new images.
//
// @param URL		(string)	New URL for the app-specific images.
// @group images
//<
setSkinImgDir : function (URL) {
	this.getPrototype().skinImgDir = URL;
},

//>	@classMethod	Canvas.getSkinImgDir()
// Return the image directory for this class of widgets, prepended with the Page image
// directory.
//
// @group images
// @return (URL)	Image directory (including Page image directory) for this widget.
//<
getSkinImgDir : function () {
	return isc.Page.getSkinImgDir(this.getPrototype().skinImgDir);
},

//<!BackCompat

// --------------------------------------------------------------------------------------------

// see JSDoc for instance method canvas.getImgURL()
_skinPrefix : "[SKIN]",
getImgURL : function (src, imgDir, instance) {
	// if no src specified, return empty string
	if (src == null || isc.isAn.emptyString(src)) return isc._emptyString;

    // get skin / app dir settings from the passed-in instance or use this class' instance
    // prototype to get instance defaults.
    instance = instance || this.getPrototype();

	// handle src being specified as an object, of the form:  {src:"URL", imgDir:"URL"}
    if (src.imgDir != null && imgDir == null) imgDir = src.imgDir;
    if (src.src != null) src = src.src;

	// default the imgDir as appropriate
	if (imgDir == null) {
		imgDir = (isc.startsWith(src, this._skinPrefix) ? instance.skinImgDir : instance.appImgDir);
	}
	var URL = isc.Page.getImgURL(src, imgDir);

    //>DEBUG
    //this.logDebug("getImgURL("+src+","+imgDir+") returned " + URL);
    //<DEBUG
	return URL;
},

//> @classMethod Canvas.setShowCustomScrollbars()
// Whether to use the browser's native scrollbars or SmartClient-based scrollbars by default
// for all canvases.
// <P>
// This method changes the default value of +link{canvas.showCustomScrollbars}.
// @param showCustomScrollbars (boolean) whether to show custom (SmartClient-based) scrollbars
//   rather than css-scrollbars by default.
// @visibility external
//<
// This is just an equivalent to calling isc.Canvas.addProperties({showCustomScrollbars:true});
// Useful to have as a static setter for SGWT.
setShowCustomScrollbars : function (showCS) {
    isc.Canvas.addProperties({showCustomScrollbars:showCS});
    
},

// Printing
// --------------------------------------------------------

// for printHTML
printOmitControls : [
"Button","StretchImgButton","ImgButton","MenuButton",
"Toolbar","ToolStrip","ButtonItem","ToolbarItem"
],
printIncludeControls : [
"Label"
],

//> @classMethod Canvas.getPrintHTML()
// Returns print-formatted HTML for the specified list of components.
//
// @param components (Array of Canvas) Components to get the print HTML for. Strings of raw HTML may
//  also be included in this array, and will be integrated into the final HTML at the appropriate
//  point.
// @param printProperties (PrintProperties) properties affecting print output
// @param callback (callback) Callback to fire when the method completes. The generated print HTML
//  will be passed in as the first parameter <code>HTML</code>.
// @param [separator] (HTML) Optional HTML separator to render between each component's printable HTML
// @return (HTMLString) print HTML for the components passed in
// @visibility external
//<
// callback is also passed the callback as a second parameter to allow the developer to pass
// state around.
// HTML / index params are used internally - this method calls itself to handle asynchronous HTML
// generation.
getPrintHTML : function (components, printProperties, callback, separator, HTML, index) {

    if (!isc.isAn.Array(components)) components = [components];

    if (HTML == null) HTML = [];
    if (index == null) index = 0;

    var async,
        componentCallback = {target:this, methodName:"gotComponentPrintHTML",
                             components:components, printProperties:printProperties,
                             callback:callback, HTML:HTML, index:index, separator:separator};

    for (; index < components.length; index++) {
        // if we fire the component level callback - start on the component after it in the list!
        componentCallback.index+=1;
        var component = components[index];

        // allow raw HTML strings
        var compHTML;
        if (isc.isA.String(component)) compHTML = component;
        else compHTML = component.getPrintHTML(printProperties, componentCallback);

        if (compHTML != null) {
            HTML.add(compHTML);
        } else {
            async = true;
            break;
        }
    }

    // if we went asynchronous, we'll run again
    if (async) {
        if (!callback) {
            this.logWarn("getPrintHTML(): HTML generated asynchronously, but no callback passed in");
        }
        return null;
    }
    if (callback) {
        this.fireCallback(callback, "HTML,callback", [HTML.join(separator || isc.emptyString),
                                                      callback]);
    }

    return HTML.join(separator || isc.emptyString);
},


gotComponentPrintHTML : function (HTML, callback) {
    callback.HTML.add(HTML);
    this.getPrintHTML(callback.components, callback.printProperties, callback.callback,
                      callback.separator, callback.HTML, callback.index);
},


// HTML for Images (and other basic structures)
// --------------------------------------------------------------------------------------------

//>	@classMethod Canvas.imgHTML()
//			Return the HTML for an image.
//
//		@group	images
//		@param	src				(SCImgURL)
//		NOTE: instead of passing several parameters, you can pass an object as the 'src'
//      parameter with properties for all the various function parameters with, eg:<br>
//      canvas.imgHTML( {src:"foo", width:10, height:10} );
//		@param	[width]			(number)
//		@param	[height]		(number)
//		@param	[name]			(string)
//		@param	[extraStuff]	(string)
//		@param	[imgDir]		(string)
//
//		@return	(string)	configured IMG tag
//<

getImgHTML : function (src, width, height, name, extraStuff, imgDir, activeAreaHTML,
                       instance, returnTemplate, generateSpan) {
	return this.imgHTML(src, width, height, name, extraStuff, imgDir, activeAreaHTML,
                        instance, returnTemplate, generateSpan);
},

_getImgHTMLTemplate : function (src, width, height, name, extraStuff, imgDir, activeAreaHTML, generateSpan) {
    return isc.Canvas.imgHTML(src, width, height, name,
                              extraStuff, imgDir, activeAreaHTML, null, true, generateSpan);
},

// - if "instance" is passed, we into account the instance settings for imgDir and make the IMG
//   ID unique to the instance.
// - if "returnTemplate" is passed, we return an HTML template Array, with a slot open to give
//   a unique ID to the image.  This is a very advanced internal API for generating many images
//   with the same SRC, size and other attributes but with different unique IDs.
_imgMapId : 0,
_$apos:"&apos;",
_imgAlignToVerticalAlignMap: {
    "TEXTTOP": "text-top",
    "texttop": "text-top",
    "absmiddle": "middle"
},

_nullSrcPlaceholder: new String,
imgHTML : function (src, width, height, name, extraStuff, imgDir, activeAreaHTML,
                    instance, returnTemplate, generateSpan) {

    var align,
        extraCSSText;
	// if an object is passed in for SRC, assume that it's a properties object
	//	and normalize it into the arguments of the function
	if (isc.isAn.Object(src)) {
		if (src.width != null) 			width = src.width;
		if (src.height != null) 		height = src.height;
		if (src.name != null) 			name = src.name;
		if (src.extraStuff != null) 	extraStuff = src.extraStuff;
		if (src.imgDir != null) 		imgDir = src.imgDir;
        if (src.align != null)          align = src.align;
        if (src.activeAreaHTML != null)    activeAreaHTML = src.activeAreaHTML;
        if (src.generateSpan != null)   generateSpan = src.generateSpan;
        if (src.extraCSSText != null)   extraCSSText = src.extraCSSText;
		src = src.src;
	}

    if (src === this._nullSrcPlaceholder) {
        src = null;
    } else if (src == null || isc.isAn.emptyString(src)) {
        return (returnTemplate ? [isc._emptyString] : isc._emptyString);
    }

    // once ever setup
    var template = this._imgTemplate;
    if (!template) {
        this._imgSrc = "<img src='";
        this._widthEquals = "' width='";
        this._heightEquals = "' height='";
        this._alignEquals = "' align='";
        this._styleEquals = "' style='";
        this._idEquals = "' id='";
        this._nameEquals = "' name='";
        this._closeQuote = "' ";
        // NOTE: Opera converts TEXTTOP to "bottom" when retrieved from the DOM, and align is
        // way off.  "middle" seems close to what "TEXTTOP" used to mean
        // Further note: Later versions of Opera (observed in 12.01) do not treat "middle"
        // like TEXTTOP - "top" gives a better result (and also works in older Opera versions)
        this._textTop = isc.Browser.isOpera ? "top" : "TEXTTOP";
        this._endString = " border='0' suppress='TRUE'/>";
        this._imgTemplate = template = [this._imgSrc];

        

        this._alphaFilterStart =
           "' style='filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\"";
        this._alphaFilterEnd = "\",sizingMethod=\"scale\");";


        
        this._spanSimpleStart = "<span style='display:inline-block";
        this._spanSimpleStartFixARIA = "<span role='presentation' style='display:inline-block";
        this._spanStart = "<span style='display:inline-block;background-size:100% 100%;background-image:url(\"";
        this._spanStartFixPNG = "<span style='display:inline-block;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\"";
        this._spanStartFixARIA = "<span role='presentation' style='display:inline-block;background-size:100% 100%;background-image:url(\"";
        this._spanStartFixARIAFixPNG = "<span role='presentation' style='display:inline-block;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\"";
        this._widthColon = ";width:";
        this._endURLWidthColon = "\");width:";
        this._endURL = "\")";
        this._endFixPNGWidthColon = "\",sizingMethod=\"scale\");width:";
        this._endFixPNG = "\",sizingMethod=\"scale\")";
        this._pxHeightColon = "px;height:";
        this._heightColon = ";height:";
        this._pxVerticalAlignColon = "px;vertical-align:";
        this._verticalAlignColon = ";vertical-align:";

        this._endSpanString = " ></span>";
    }
    // default align to texttop (this._textTop defined above)
    if (align == null) align = this._textTop;

    if (instance != null && instance.isPrinting) generateSpan = false;

    
    if (!this._blankURL) this._blankURL = this.getImgURL("[SKIN]/blank.gif");

    //>DEBUG
    if (isc.Browser.isSafari && (width > 32000 || height > 32000)) {
        this.logWarn("Attempting to draw an image of size " + width + " x " + height +
                    ".  Images larger than 32000 pixels in either direction are not reliably " +
                    " rendered in this browser.");
    } //<DEBUG

    // if we're being asked to return a template, allocate a fresh one that the caller can hang
    // onto
    if (returnTemplate) template = [this._imgSrc];

    // fill out the template.  NOTE: if the numbering changes here, all callers to
    // _getImgHTMLTemplate() need to be updated.
    // [0] "<img src='"
    // [1] URL
    // [2] [unused]
    // [3-5] IE filtering
    // [6] "' width="
    // [7] width
    // [8] "' height="
    // [9] height
    // [10] "' align="
    // [11] align
    // [12] "' style='"
    // [13] extraCSSText
    // [14] "' name="
    // [15] canvas ID
    // [16] name
    // [17] "' usemap='" + mapName
    // [18] "' "
    // [19] extraStuff
    // [20] endString
    // [21] (optional) "<map name='.....></map>"
    // in XHTML mode

    var URL,
        fixPNG;
    if (src == null && generateSpan) {
        URL = null;
        fixPNG = false;
    } else {
        if (src == null) src = this._blankURL;

        URL = this.getImgURL(src, imgDir, instance);
        if (isc.Page.isXHTML()) {
            URL = isc.makeXMLSafe(URL);
        } else {
            URL = URL.replace(this._$singleQuote, this._$apos);
        }

        fixPNG = this._fixPNG(instance) && this._isPNG(src);
        if (fixPNG) {
            
            if (width == null) width = 16;
            if (height == null) height = 16;
        }
    }

    var fixARIA = (isc.screenReader &&
                   (extraStuff == null ||
                    (!isc.contains(extraStuff, "alt=") && !isc.contains(extraStuff, "aria-label"))));

    if (generateSpan) {
        if (src == null) {
            template[0] = fixARIA ? this._spanSimpleStartFixARIA : this._spanSimpleStart;
            template[1] = null;
            if (width) {
                template[6] = this._widthColon;
                template[7] = width;
            }
        } else {
            
            if (fixARIA) {
                template[0] = fixPNG ? this._spanStartFixARIAFixPNG : this._spanStartFixARIA;
            } else {
                template[0] = fixPNG ? this._spanStartFixPNG : this._spanStart;
            }
            template[1] = URL;
            if (width) {
                template[6] = fixPNG ? this._endFixPNGWidthColon : this._endURLWidthColon;
                template[7] = width;
            } else {
                template[6] = fixPNG ? this._endFixPNG : this._endURL;
            }
        }
        if (height) {
            template[8] = width ? this._pxHeightColon : this._heightColon;
            template[9] = height;
        }
        template[10] = height ? this._pxVerticalAlignColon : this._verticalAlignColon;

        if (this._imgAlignToVerticalAlignMap.hasOwnProperty(align)) {
            align = this._imgAlignToVerticalAlignMap[align];
        }
        template[11] = align;

        if (extraCSSText != null) {
            template[12] = isc.semi;
            template[13] = extraCSSText;
        }

        template[20] = this._endSpanString;
    } else {
        
        if (fixARIA) {
            
            template[0] = "<img role='presentation' src='";
        } else {
            template[0] = this._imgSrc;
        }
        if (!fixPNG) {
            template[1] = URL;
        } else {
            
            template[1] = this._blankURL;
            template[3] = this._alphaFilterStart;
            template[4] = URL;
            template[5] = this._alphaFilterEnd;
        }

        if (width) {
            template[6] = this._widthEquals;
            template[7] = width;
        }
        if (height) {
            template[8] = this._heightEquals;
            template[9] = height;
        }

        template[10] = this._alignEquals;
        template[11] = align;

        if (extraCSSText != null) {
            template[12] = this._styleEquals;
            template[13] = extraCSSText;
        }

        template[20] = this._endString;
    }

    if (name) {
        template[14] = isc.Page.isXHTML() || generateSpan ? this._idEquals : this._nameEquals;
        // make the name unique to the target instance if passed one
        if (instance) template[15] = instance.getCanvasName();
        template[16] = name;
    }
    // img map support
    var mapName;
    if (activeAreaHTML) {
        mapName = "ISC_IMGMAP_" + this._imgMapId++;
        template[17] = "' usemap='#" + mapName;
    }
    template[18] = this._closeQuote;

    if (extraStuff) {
        
        template[19] = extraStuff;
    }

    if (activeAreaHTML) {
        template[21] = "<map name='" + mapName + "'>" + activeAreaHTML + "</map>";
    }

    if (returnTemplate) return template;

    // otherwise return the HTML and truncate the template
    var output = template.join(isc._emptyString);
    template.length = 3;
    return output;
},


// Value Icon HTML generation
// Generates the <img ...> tag HTML used by ListGrids and DynamicForm items for their 'valueIcons'

_$IDEquals:"ID='",
_$singleQuote:"'",
_$absmiddle:"absmiddle",
_$valueIconExtraStuffTemplate: [
    ,                                                   // [0] ID=', or null
    ,                                                   // [1] ID, or null
    ,                                                   // [2] ', or null
    " eventpart='valueicon' ",                          // [3]
    null                                                // [4] extraExtraStuff
],
_$marginLeftColon: "margin-left:",
_$pxMarginRightColon: "px;margin-right:",
_valueIconObj: {},
_getValueIconHTML : function (src, prefix, width, height, leftPad, rightPad, ID, instance, extraExtraStuff) {

    // Apply ID and custom styling to the image through the 'extraStuff' parameter
    var extraStuffTemplate = this._$valueIconExtraStuffTemplate;
    if (ID != null) {
        extraStuffTemplate[0] = this._$IDEquals;
        extraStuffTemplate[1] = ID;
        extraStuffTemplate[2] = this._$singleQuote;
    } else {
        extraStuffTemplate[0] = extraStuffTemplate[1] = extraStuffTemplate[2] = null;
    }

    extraStuffTemplate[4] = extraExtraStuff;

    var iconObj = this._valueIconObj;
    iconObj.src = src === this._nullSrcPlaceholder ? this._nullSrcPlaceholder : isc.Canvas.getImgURL(src, prefix, instance);
    iconObj.width = width
    iconObj.height = height
    // We want the valueIcon to be center-aligned with adjacent text
    // (either the form item's textBox text, or a listGrid cell's text)
    // We do this by setting align='absMiddle', and vertical-align = middle
    
    if (height != null && height < 16 &&
        (isc.Browser.isMoz || isc.Browser.isSafari ||
         (isc.Browser.isIE && (isc.Browser.isIE9 || isc.Browser.version >= 10))))
    {
        iconObj.align = null;
    } else {
        iconObj.align = this._$absmiddle; // prevent default "text-top"
    }
    iconObj.imgDir = prefix;
    iconObj.extraStuff = extraStuffTemplate.join(isc.emptyString);
    iconObj.extraCSSText = this._$marginLeftColon + (leftPad || 0) +
                           this._$pxMarginRightColon + (rightPad || 0) + isc.px;
    // The width and height passed to _getValueIconHTML() frequently differs from the image's
    // intrinsic width and height. Before writing out a `span' instead of an `img', make sure
    // that the `background-size:100% 100%' that we would write out will have an effect in this
    // browser.
    iconObj.generateSpan = ((instance == null || !instance.isPrinting) &&
                            this._generateSpanForBlankImgHTML &&
                            isc.Browser._supportsBackgroundSize);

    return isc.Canvas.imgHTML(iconObj);
},

// NOTE: Whether to apply IE5.5+ PNG alpha transparency workaround.
// IE7 natively supports PNG transparency, however if you also set opacity via the
// Microsoft.Alpha filter, PNG transparency breaks.
// This is visible with fade animations, and with transparent hovers with dropShadows (since
// the shadows, which are peers, get the master's transparency),
// This is obliquely mentioned in the blog where PNG transparency support was first announced:
// - http://blogs.msdn.com/ie/archive/2005/04/26/412263.aspx
// Getting rid of filters greatly reduces browserDoneDrawing() time, so it might be a
// worthwhile optimization to special case certain PNG-heavy widgets, like so:
//  - in _fixPNG(), allow an instance flag that avoids using filter hacks for PNG transparency so
//    long as opacity is not set
//  - set this flag for DropShadow only
//  - in setOpacity() override on DropShadow, redraw() to cause filters to be used
//  - NOTE: in order to generalize this to all Canvii or even all EdgedCanvas, would need
//    parent->child opacityChange notifications since setOpacity can be called on a parent.
//
// - Update: 6/15/2007 IE 7.0.5730.11
// IE7 has blurriness at PNG edge on a PNG *without* filters if a filter is used elsewhere on
// the page.  An example is here:
//   http://www.atalasoft.com/cs/blogs/davidcilley/archive/2007/03/14/ie7-dximagetransform-and-png-transparency-problem.aspx
// For single pixel tiled PNGs, which SmartClient uses extensively for the center segment of
// buttons and for the "rails" on rounded corners, this blur translates to what looks like
// a fade effect on the stretched image.
// Note that this effect is avoided for a PNG that has no alpha channel at all, which is a
// distinct file format from a PNG with an alpha channel with 100% opacity.  In many cases the
// alpha channel could be removed, but not for, eg, tintable SectionHeaders.
//
// This basically means it's impossible to avoid using the AlphaImageLoader workaround for PNGs
// unless we *both* do not use any IE filters within the framework *and* insist that all
// developers who use SmartClient also do not use IE filters
//
// IE 8 (version 8.0.6001.18702) tested  6 Aug 09:
// The issue with other filters causing stretch pngs to "fade" has been resolved.
// However the underlying issue where if you also set opacity via the
// Microsoft.Alpha filter, PNG transparency breaks still occurs.
// Note that we can't just apply this workaround where opacity is specified on a widget since we'd
// also have to check up the parentElement chain to the topmost widget to be sure none of them had
// a specified transparancy != 100.
// Note: still no support for setting handle.style.opacity directly in IE8
//
// Note that we now disable filters by default as part of skinning (by setting
// neverUseFilters), which causes us to avoid this workaround in most cases.
//
// IE9 (running in IE9 mode / HTML5 doctype) renders this workaround obsolete in all cases
// as far as we know.
_fixPNG : function (instance) {
    // if we're explicitly not using the png fix, return false immediately.
    if (this.usePNGFix == false) return false;
	var fix = isc.Browser.isIE && isc.Browser.minorVersion >= 5.5 &&
	          !isc.Browser.isIE9 &&
//                (isc.Browser.version < 7 || this.opacity == null) &&
                isc.Browser.isWin &&
                (!isc.Canvas.neverUseFilters && this.neverUsePNGWorkaround != true);
    // if we have an instance with _fixPNG returning false, respect it
    if (fix && instance && instance._fixPNG && !instance._fixPNG()) {
        fix = false;
    }
    return fix;
},

_$pngSuffixes:{
png:true, PNG:true, Png:true

},
_isPNG : function (src) {
	return (src && this._$pngSuffixes[src.substring(src.lastIndexOf(isc.dot) + 1)]);
},

_setImageURL : function (element, src, imgDir, instance) {
    // apply the new URL
    var style = element.style,
        isSpan = element.tagName === "SPAN",
        URL,
        fixPNG;
    if (src === this._nullSrcPlaceholder && isSpan) {
        URL = null;
        fixPNG = false;
    } else {
        if (src === this._nullSrcPlaceholder) src = this._blankURL;
        URL = this.getImgURL(src, imgDir, instance);
        fixPNG = this._fixPNG(instance) && this._isPNG(src);
    }
    if (fixPNG) {
        style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\"" + URL + "\",sizingMethod=\"scale\")";
        if (!isSpan) element.src = this._blankURL;
    } else {
        if (isSpan) {
            if (src === this._nullSrcPlaceholder || !src) {
                style.backgroundSize = isc._emptyString;
                style.backgroundImage = isc._emptyString;
            } else {
                style.backgroundImage = "url(\"" + URL + "\")";
            }
        } else {
            style.filter = isc._emptyString;
            element.src = URL;
        }
    }
},

//> @classMethod Canvas.linkHTML()
// Returns the HTML for a standard link (anchor) element.
// @param href (string) target url for the link.
// @param [text] (HTMLString) HTML to display in the link element - if null, use the href
// @param [target] (string) target window for the link - defaults to "_blank"
// @param [ID] (string) optional ID for the link element
// @param [tabIndex] (number) optional tabIndex for the link
// @param [accessKey] (string) optional accessKey for the link
// @return (HTMLString) HTML for the link
// @visibility internal
//<
// @param extraStuff - allows you to add freeform attributes into the tag)

_$linkHTMLTemplate:[
    "<a",       // 0
    ,           // 1: (ID ? " ID='" + ID + "'" : ""),
    " href='",  // 2
    ,           // 3: href
    "' target='", // 4
    ,           // 5: target
    "'",        // 6
    ,           // 7: tabIndex = tabIndex or null
    ,           // 8: accessKey = accessKey or null
    ,           // 9: extraStuff
    ">",        // 10
    ,           // 11 text of the link
    "</a>"
],
linkHTML : function (href, text, target, ID, tabIndex, accessKey, extraStuff) {

    if (text == null) {
        text = (!href ? isc.nbsp : String(href).asHTML());
    }

    var template = this._$linkHTMLTemplate;

    if (ID != null) template[1] = " ID='" + ID + "'";
    else template[1] = null;

    template[3] = String.asAttValue(href);
    template[5] = (target ? String.asAttValue(target) : "_blank");

   if (tabIndex != null) template[7] = " tabIndex=" + tabIndex;
   else template[7] = null;

   if (accessKey != null) template[8] = " accessKey='" + accessKey + "'";
   else template[8] = null;

   if (extraStuff) template[9] = " " + extraStuff;

   template[11] = text;
   return template.join(isc.emptyString);
},




//>	@classMethod	Canvas.blankImgHTML()
//			Return the HTML for a blank image, such as would be used for a spacer.
//		@group	utils
//
//		@param	[width]			(number)
//		@param	[height]		(number)
//
//		@return	(string)	configured IMG tag
//<
_blankImgURL : "[SKINIMG]/blank.gif",
_$zero:"0",

_generateSpanForBlankImgHTML: !isc.Browser.isIE || isc.Browser.version >= 9,
blankImgHTML : function (width, height) {
    var template = this._blankTemplate;
    if (!template) {
        
        
        template = this._blankTemplate =
            this._getImgHTMLTemplate(this._blankImgURL, 1, 1, null, null, null, null,
                                     this._generateSpanForBlankImgHTML);
    }
    template[7] = width || this._$zero;
    template[9] = height || this._$zero;
    return template.join(isc._emptyString);
},

//>	@classMethod	Canvas.spacerHTML()
//		Return the HTML for a blank spacer at a particular width and height.
//		Does this without using images so it should be really fast.
//
//		@group	utils
//
//		@param	[width]			(number)
//		@param	[height]		(number)
//
//		@return	(string)	HTML for the spacer
//<
spacerHTML : function (width, height, contents) {
    // shortcut: if the size is 0x0, return an empty string
    if (width == 0 && height == 0) return isc._emptyString;

    
    if (isc.Browser.isMoz ||
        isc.Browser.isSafari ||
        isc.Browser.isOpera ||
        isc.Browser.isStrict ||
        (isc.Browser.isIE && (isc.Browser.version >= 10 ||
                              (height < 3 && (isc.Browser.minorVersion == 5.5 || isc.Browser.isMac)))))
    {
        
        var threshold;
        if (isc.Browser.isSafari) {
            threshold = 32000;
        } else if (isc.Browser.isFirefox && isc.Browser.geckoVersion >= 20090219) {
            threshold = 17895580;
        } else if (isc.Browser.isIE && isc.Browser.isStrict) {
            threshold = 16000;
        }

        if (threshold != null && (width > threshold || height > threshold)) {
            var output = isc.SB.create(),
                max = threshold,
                // note - numRows / cols will be one less than is required
                numRows = Math.floor(height / max),
                numCols = Math.floor(width / max);

            output.append("<TABLE role='presentation' CELLPADDING=0 CELLSPACING=0 BORDER=0 MARGIN=0>");
            for (var i = 0; i <= numRows; i++) {
                output.append("<TR>");
                for (var j = 0; j <= numCols; j++) {

                    output.append("<TD>");
                    // write cells down leading diagonal, or along first row / col if we've
                    // already writen out all the cells we need on the other axis
                    var writeSpacer =
                        ((i == j) || (i > numCols && j == 0) || (j > numRows && i == 0));

                    if (writeSpacer) {
                        var cellSpacerHeight = (i < numRows ? max : height - (i*max)),
                            cellSpacerWidth = (j < numCols ? max : width - (j*max));

                        output.append(this.blankImgHTML(cellSpacerWidth, cellSpacerHeight));
                    }
                    output.append("</TD>");
                }
                output.append("</TR>");
            }
            output.append("</TABLE>");
            return output.toString();
        }
        return this.blankImgHTML(width,height);
    }

    // in IE8 non-strict mode a limit has been hit whereby spans exceeding ~140,000px in height
    // start misreporting their heights.
    // Simply stacking them one after another using <br> tags to break them up resolves this
    // (tested up to 14,000,000px)
    var vThreshold = 1300000;
    if (height > vThreshold) {
        var spacerStrings = [];
        var cumulativeHeight = 0;
        while (cumulativeHeight < height) {

            var lastRow, blockHeight;
            if (cumulativeHeight + 1400 >= height) {
                lastRow = true;
                blockHeight = height-cumulativeHeight;
            } else {
                blockHeight = 1400;
                lastRow = false;
            }
            spacerStrings[spacerStrings.length] = this.spacerHTML(width,blockHeight);
            spacerStrings[spacerStrings.length] = "<br>";

            cumulativeHeight += blockHeight;

        }
        return spacerStrings.join(isc._emptyString);
    }


    // return HTML that the browser recognizes as taking up space.
    var spacerHTML = this._spacerHTMLTemplate;
    if (spacerHTML == null) {
        spacerHTML = this._spacerHTMLTemplate = [
            "<SPAN STYLE='WIDTH:",
            null, // width
            "px;HEIGHT:",
            null, // height
            "px;overflow:hidden;'>",
            null, // contents
            "</SPAN>"
        ];
    }
    spacerHTML[1] = width;
    spacerHTML[3] = height;
    spacerHTML[5] = contents ? contents : isc.nbsp;
    return spacerHTML.join(isc._emptyString);
},

//>	@classMethod canvas.hiliteCharacter()	(A)
//			Given a string and a character, hilite the first occurrence of the character in the
//          string (if it occurs), preferring uppercase to lowercase.
//
//		@group	utils
//
//		@param	string      (string)    String to return with hilited character
//		@param	character   (character) Character to hilite
//		@param	[hilitePrefix] (string) Prefix to apply to hilighted character - defaults to
//                                      "&lt;span style='text-decoration:underline;'&gt;"
//      @param  [hiliteSuffix]  (string)    Suffix to apply to hilited character - defaults to
//                                          "&lt;/span&gt;"
//
//		@return	(string)	The string passed in, with the first occurrence of the hilite
//                          character enclosed by the 'hilitePrefix' and 'hiliteSuffix'
// @visibility external
//<
// This is used by form items, and stretchImgButtons to hilite their accessKey
hiliteCharacter : function (string, character, hilitePrefix, hiliteSuffix) {

    if (!isc.isA.String(string) || !isc.isA.String(character) || character.length != 1)
        return string;

    // Bail if they're attempting to hilight a space character - it will look weird!
    
    if (character == " ") return string;

    // Default the hilite prefix and suffix if necesary (note - we don't support being passed
    // just one of these arguments)
    if (hilitePrefix == null || hiliteSuffix == null) {
        hilitePrefix = "<span style='text-decoration:underline;'>";
        hiliteSuffix = "</span>"
    }

    var index = string.indexOf(character.toUpperCase());
    if (index == -1) index = string.indexOf(character.toLowerCase());

    if (index != -1) {
        var start = string.slice(0, index),
            hiliteString = string.slice(index, index+1),
            end = string.slice(index+1);

        hiliteString = hilitePrefix+hiliteString+hiliteSuffix;
        string = start.concat(hiliteString, end);
    }

    return string;
},

// Redraw Queue
// --------------------------------------------------------------------------------------------

//>	@classMethod	Canvas.scheduleRedraw()	(A)
// 			Add a canvas that needs to be redrawn to the redrawQueue so it will be redrawn
//          automatically.  Called by Canvas.markForRedraw()
//		@group	draw
//
//		@param	canvas		(Canvas)		Canvas to be redrawn
//<
_$clearRedrawQueue:"clearRedrawQueue",
scheduleRedraw : function (canvas) {
    //this.logWarn("Scheduled redraw of: " + canvas + this.getStackTrace());

	// add the canvas to the list of canvases to be redrawn
    if (canvas && canvas.priorityRedraw) {
        this._redrawQueue.addAt(canvas, 0);
    } else {
        this._redrawQueue.add(canvas);
    }
	// and start the timer to redraw the objects in the queue
	if (!this._redrawTimer) {
		this._redrawTimer =
            isc.Timer.setTimeout({target:isc.Canvas, methodName:this._$clearRedrawQueue}, this._redrawQueueDelay);
	}
},

//>	@classMethod	Canvas.clearRedrawQueue()	(A)
// 		Redraw all the canvases that are currently waiting on a redraw
//		@group	draw
//<
clearRedrawQueue : function () {
    // don't show a new thread if we're being called from a timer
    var inThread = (isc.EH._thread != null && isc.EH._thread.contains("TMR"));
    if (!inThread) isc.EH._setThread("RDQ");

    //>DEBUG
    var start = isc.timeStamp();
    //<DEBUG

	// set the timer to null so that a new timer can be started if further redraws are scheduled.
    // We don't have to clearTimeout because the timeout already fired.
	this._redrawTimer = null;

	// get the list of items to be redrawn
	var list = this._redrawQueue;
    // create a new list for additional redraws (some of which may be triggered by the redraws
    // we do now!)
	this._redrawQueue = [];

	//>DEBUG
	if (this.logIsDebugEnabled()) {
		var redrawList = "";
		for (var i = 0; i < list.length; i++) {
			redrawList += list[i];
			if (i != list.length - 1) redrawList += ", ";
		}
		this.logDebug("clearRedrawQueue: " + redrawList, "drawing");
    }
	//<DEBUG

    // priorityRedraw: these items need to repaint as soon as possible, so postpone any other
    // redraws, to allow the browser to repaint the screen.
    var item, priorityList;
	for (var i = 0; i < list.length; i++) {
        item = list[i];
        if (item && item.priorityRedraw) {
            item.priorityRedraw = false; // clear the flag (it applies for one redraw only)
            if (priorityList == null) priorityList = [];
            priorityList.add(item);
            list[i] = null;
        }
    }
    if (priorityList != null) {
        //>DEBUG
        this.logInfo("Priority redraw: postponing non-priority items", "drawing");
        //<DEBUG
        this._redrawQueue = list;
        this.scheduleRedraw(list[0]); // HACK kick off the timer
        list = priorityList;
    }

	// now redraw each item in the list
    var redraws = 0, item;
	for (var i = 0; i < list.length; i++) {
        item = list[i];
        // ignore items that were destroyed right after being marked for redraw
        if (item == null || item.destroyed) continue;
		// avoid redrawing if an item has already been redrawn
		if (item && item.isDirty()) {
            // redraw the item
			item.redraw(false);
            redraws++;
		}
	}
    //>DEBUG
    if (this.logIsDebugEnabled("redraws")) {
        this.logDebug("clearRedrawQueue: " + redraws + " redraws (" + list.length + " items), " +
                      (isc.timeStamp() - start) + "ms"
                      //+ " queue was: " + list
                      , "redraws");
    }
    //<DEBUG
    if (!inThread) isc.EH._clearThread();
},




// Delayed adjustOverflows
// --------------------------------------------------------------------------------------------
// Add a canvas to the queue to have overflow adjusted after a delay, and set timer running (if
// necessary)
// See comments in 'adjustOverflow()' for description of why this function is used.
_queueForDelayedAdjustOverflow : function (canvasID) {
    if (!isc.Canvas._delayedAdjustOverflowQueue) isc.Canvas._delayedAdjustOverflowQueue = [];
    isc.Canvas._delayedAdjustOverflowQueue.add(canvasID);

    if (!isc.Canvas._delayedAdjustTimer) {
        isc.Canvas._delayedAdjustTimer =
            isc.Timer.setTimeout({target:isc.Canvas, methodName:"_clearDelayedAdjustOverflowQueue"},
                                 isc.Canvas._delayedAdjustOverflowQueueDelay)
    }

},

// Adjust overflows of all the canvii in the 'delayedAdjustOverflowQueue'
_clearDelayedAdjustOverflowQueue : function () {
    var array = isc.Canvas._delayedAdjustOverflowQueue;

    // clear the queue and the timer pointer
    isc.Canvas._delayedAdjustOverflowQueue = [];
    isc.Canvas._delayedAdjustTimer = null;

    if (!array || array.length == 0) return;

    for (var i = 0; i < array.length; i++) {
        // call adjustOverflow on each widget in the queue.
        // Note - if the Canvas still can't successfully adjustOverflow(), that method will
        // re-queue the widget for delayed adjustOverflow().
        var canvas = window[array[i]];
        if (isc.isA.Canvas(canvas)) canvas.adjustOverflow("delayed");
    }

},

// Check for browser size change
checkForPageResize : function () {
	// Pass the "polling" argument into _pageResize. This indicates the method wasn't called by
	// an actual browser resize event and will cause it to no-op if appropriate.
	isc.EH._pageResize(true);
},


// Move a canvas offscreen (used by Layout for pre-drawing to determine overflowed sizes)
moveOffscreen : function (member) {
    if (member.isDrawn()) return;
    
    var moveOffscreen = (!(!isc.Browser.isWin && isc.Browser.isMoz &&
                           member.showCustomScrollbars == false &&
                           (member.overflow == isc.Canvas.AUTO)));

    if (moveOffscreen) member.moveTo(null, -9999);
},

// --------------------------------------------------------------------------------------------


//>	@classMethod	Canvas.scheduleDestroy()	(A)
// 			Add a canvas that needs to be destroyed to the destroyQueue so it will be destroyed
//          automatically.  Called by Canvas.markForDestroy()
//		@group	draw
//
//		@param	canvas		(Canvas)		Canvas to be destroyed
//<
_destroyQueue:[],
_destroyQueueDelay:0,
_$clearDestroyQueue:"clearDestroyQueue",
scheduleDestroy : function (canvas) {

    if (!canvas || canvas.destroyed || canvas.destroying || !canvas.destroy) return;

    this._destroyQueue.add(canvas);

	// and start the timer to destroy the objects in the queue
	if (!this._destroyTimer) {
		this._destroyTimer =
            isc.Timer.setTimeout({target:isc.Canvas, methodName:this._$clearDestroyQueue}, this._destroyQueueDelay);
	}
},

//>	@classMethod	Canvas.clearDestroyQueue()	(A)
// 		Destroy all the canvases that are currently waiting on a destroy()
//		@group	draw
//<
clearDestroyQueue : function () {
    isc.EH._setThread("DSQ");

    //>DEBUG
    var start = isc.timeStamp();
    //<DEBUG

	// set the timer to null so that a new timer can be started if further destroys are scheduled.
    // We don't have to clearTimeout because the timeout already fired.
	this._destroyTimer = null;

	// get the list of items to be destroyed
	var list = this._destroyQueue;

    // create a new list for additional destroys
	this._destroyQueue = [];

	//>DEBUG
	if (this.logIsDebugEnabled("destroys")) {
		var destroyList = "";
		for (var i = 0; i < list.length; i++) {
			destroyList += list[i];
			if (i != list.length - 1) destroyList += ", ";
		}
		this.logDebug("clearDestroyQueue: " + destroyList, "destroys");
    }
	//<DEBUG

	// destroy each item in the list
    var destroys = 0, item;
	for (var i = 0; i < list.length; i++) {
        item = list[i];
        // ignore items that are already destroyed
        if (item == null || item.destroyed || item.destroying) continue;
        item.destroy(false);
        destroys++;
	}
    //>DEBUG
    if (this.logIsDebugEnabled("destroys")) {
        // this statistic may be misleading since we may include children or peers of items already
        // in the list, in which case they'll be destroyed, but the count won't be incremented
        this.logDebug("clearDestroyQueue: " + destroys + " direct destroy() calls (" + list.length + " items), " +
                      (isc.timeStamp() - start) + "ms"
                      //+ " queue was: " + list
                      , "destroys");
    }
    //<DEBUG
    isc.EH._clearThread();
},



// helper method used to outset or inset a canvas by a certain number of pixels.
outsetRect : function (rect, outset) {
    if (!outset) return rect;

    // rect can be like the output of Canvas.getRect()
    if (isc.isAn.Array(rect)) {
        rect[0] -= outset;
        rect[1] -= outset;
        rect[2] += 2*outset;
        rect[3] += 2*outset;
        return rect;
    }
    // or rect can be a properties block
    rect.left -= outset;
    rect.top -= outset;
    rect.width += 2*outset;
    rect.height += 2*outset;
    return rect;
},

// helper: returns true if rect1 and rect2 intersect, false othewise
rectsIntersect : function (rect1, rect2) {
    var left1 = rect1[0],
        top1 = rect1[1],
        width1 = rect1[2],
        height1 = rect1[3],

        left2 = rect2[0],
        top2 = rect2[1],
        width2 = rect2[2],
        height2 = rect2[3],

        horizontal = ((left1 > left2 + width2 - 1) || (left1 + width1 - 1 < left2)),
        vertical = ((top1 > top2 + height2 - 1) || (top1 + height1 - 1 < top2))
    ;

    return !horizontal && !vertical;
},


_forceNativeTabOrderUpdate : function () {
    if (!this.__tabIndexRefreshDiv) {
        this.ns.Element.createAbsoluteElement(
            "<DIV ID='_isc_tabIndexRefreshDiv'" +
            " style='position:absolute;left:0px;top:-100px'>&nbsp;</DIV>");
        this.__tabIndexRefreshDiv = document.all["_isc_tabIndexRefreshDiv"];
    } else {
        this.__tabIndexRefreshDiv.innerHTML = "&nbsp;"
    }
},

// maintain a list of top level canvii - this simplifies iterating through all the canvii
// in the same parent as a widget (commonly required for zIndices)
_topCanvii : [],
_addToTopLevelCanvasList : function (canvas) {
    if (!isc.isA.Canvas(canvas) || canvas._topCanviiIndex != null) return;

    this._topCanvii.add(canvas);
    canvas._topCanviiIndex = this._topCanvii.length - 1;
},

_removeFromTopLevelCanvasList : function (canvas) {
    if (!isc.isA.Canvas(canvas) || canvas._topCanviiIndex == null) return;

    this._topCanvii[canvas._topCanviiIndex] = null;
    canvas._topCanviiIndex = null;
},



// -----------------
// RTL adjustments


_adjustScrollLeftForRTL : function (left, scrollWidth, viewportWidth,
    zeroToNegativeOrigin, adjustForNegativeOrigin, adjustForInverseOrigin)
{
    if (adjustForNegativeOrigin) {
        var offset = scrollWidth - viewportWidth;
        // offset is a positive value since the scrollWidth exceeds viewport with
        // (for scrollLeft to have any meaning!).
        // To go from zero based coords to negative origin coords we need to reduce
        // by the offset.
        // To go from negative origin coords to zero based coords we need to
        // increase by the offset.
        if (zeroToNegativeOrigin) left -= offset;
        else left += offset

    } else if (adjustForInverseOrigin) {
        var maxScroll = scrollWidth - viewportWidth;
        
        if (maxScroll > 0 && maxScroll >= left) {
            left = maxScroll - left;
        }
    }
    return left;
},


// ClickMask
// --------------------------------------------------------------------------------------------

// NOTE: BackCompat only.  Canvas instance methods should be used instead (because they provide
// more context), or for very advanced callers, the EventHandler APIs should be used directly.
showClickMask : function (clickAction, mode, unmaskedTargets) {
    return this.ns.EH.showClickMask(clickAction, mode, unmaskedTargets);
},
hideClickMask : function (ID) { this.ns.EH.hideClickMask(ID); },

// ----------------------------------------------------------------------------------------

// _placeRect() - place one rectangle adjacent to another, on a specified side, without going
// offscreen.  Takes:
// - size of rectangle to place
// - coordinates / size for rectangle to place near
// - a side
// Returns X/Y coords
// Other params:
// [canOcclude]    (boolean)
//          This property controls whether this canvas can be positioned on top of the other
//          widget if there isn't room to put it next to the other widget without going off
//          screen.<br>
//          If 'canOcclude' is true, simply shift this widget over the other widget, so that
//          it ends up onscreen.  If 'canOcclude' is false, avoid extending offscreen
///         by positioning this widget on the other side of the other widget.
// [otherAxisAlign]    (string)
//   Can be one of "left", "right", "outside-left", "outside-right", "top", "bottom",
//   "outside-top", "outside-bottom". (Defaults to "left" if side is "top" or "bottom",
//   "top" if side is "left" or "right").
//   This property determines how this widget will be aligned with the other widget on the
//   other axis.
// If there isn't enough room to avoid the widget going offscreen on one axis or the other,
// allow it to push offscreen on the bottom / left side, since we can always scroll in that
// direction.
_placeRect : function (width, height, adjacentRect, side, canOcclude, otherAxisAlign) {
    // Default any optional params / normalize into expected structures

    if (isc.isAn.Array(adjacentRect)) {
        adjacentRect = {left:adjacentRect[0], top:adjacentRect[1],
             width:adjacentRect[2], height:adjacentRect[3]};

    // if passed no target rect, use the mouse position
    } else if (adjacentRect == null) {
        adjacentRect = {
            left:this.ns.EH.getX(), top:this.ns.EH.getY()
        }
    }

    // [if target rect has no width/height assume to be a point]
    if (adjacentRect.width == null) adjacentRect.width = 0;
    if (adjacentRect.height == null) adjacentRect.height = 0;

    // default side to "bottom"
    if (side == null) side = "bottom";

    // default canOcclude to true
    if (canOcclude == null) canOcclude = true;

    // we are placing the widget on a particular side; otherAxisAlign specifies where along
    // that side we want the widget to appear.  For example for a widget placed on the top or
    // bottom side, options in left-right order are "outside-left", "left" (aka "inside-left"),
    // "right" (aka "inside-right"), "outside-right".  Analogous choices for vertical placement
    // when placing on right/left side.
    // If unset, (or set to a value on the wrong axis), default to "left" when side is bottom/
    // top, and "top" when side is left/right
    var vertical = (side == "bottom" || side == "top");
    if (vertical) {
        // only 4 options on each axis - setting to "top" / "bottom" has no meaning if placing
        // above/below
        if (otherAxisAlign == "inside-right") otherAxisAlign = "right";
        if (otherAxisAlign != "right" &&
            otherAxisAlign != "outside-right" &&
            otherAxisAlign != "outside-left") otherAxisAlign = "left";
    } else {
        if (otherAxisAlign == "inside-bottom") otherAxisAlign = "bottom";
        if (otherAxisAlign != "bottom" &&
            otherAxisAlign != "outside-bottom" &&
            otherAxisAlign != "outside-top") otherAxisAlign = "top";
    }

    var left = adjacentRect.left;
    if (vertical) {
        // if otherAxisAlign is "left", we want to put it at the left edge (no change to left)
        // Adjust for other options:
        if (otherAxisAlign == "right") left += (adjacentRect.width - width);
        else if (otherAxisAlign == "outside-right") left += adjacentRect.width;
        else if (otherAxisAlign == "outside-left") left -= width;
    } else {
        if (side == "left") left -= width;
        else left += adjacentRect.width
    }

    var top = adjacentRect.top;
    if (vertical) {
        if (side == "top") top -= height;
        else top += adjacentRect.height;
    } else {
        if (otherAxisAlign == "bottom") top += (adjacentRect.height - height);
        else if (otherAxisAlign == "outside-bottom") top += adjacentRect.height;
        else if (otherAxisAlign == "outside-top") top -= height;

    }

    // left / top now represent the desired position.  Adjust this to avoid the placed rect
    // from sticking offscreen if necessary.
    // Note: If canOcclude is true, this is simple, we will just move it back as far as
    // necessary to avoid being clipped by the browser viewport.
    // If canOcclude is false, we must "jump" across the adjacentRect to avoid covering it,
    // so we will try placing it on the opposite side, instead.
    var pageWidth = isc.Page.getWidth(),
        pageHeight = isc.Page.getHeight(),
        // param will give us negative origin coords if we're in RTL mode
        pageScrollLeft = isc.Page.getScrollLeft(true),
        pageScrollTop = isc.Page.getScrollTop()
    ;

    // calculate how much we're jutting out beyond the browser viewport in each dimension
    var leftExcess = pageScrollLeft - left,
        rightExcess = left + width - (pageWidth + pageScrollLeft),
        topExcess = pageScrollTop - top,
        bottomExcess = top + height - (pageHeight + pageScrollTop);
    ;

    // Shortcut: if the rectangle will be completely onscreen, just return it:
    if (leftExcess <=0 && rightExcess <=0 && topExcess <=0 && bottomExcess <=0) {
        return [left, top];
    }

    // for each direction we extend out of the viewport:
    // - if we are allowing occlusion, just move top and left until not sticking out of the
    //   viewport
    // - otherwise, try moving to the other side of the adjacent rect, and use that position if
    //   it prevents sticking out of the viewport.  If moving to the other side still has us
    //   sticking out of the viewport, always prefer sticking out to the right/bottom, since
    //   the user can scroll in that direction.

    // -- HORIZONTAL ADJUSTMENTS:
    // jutting out to the left
    if (leftExcess > 0) {
        // If we're on the left side, and canOcclude is false, we want to jump to the right
        // side of the adjacentRect
        if (side == "left" && !canOcclude) {
            // Edge cases [no pun intended]:
            // - the adjacentRect is completely offscreen to the left
            //   * In this case, we will move past the right edge to ensure our rect is
            //     onscreen [move to pageScrollLeft]
            // - positioning at the right edge of adjacentRect will push our new rect offscreen
            //   to the right
            //   * This is ok - preferable to be offscreen on the right since the user can
            //     always scroll to reach it
            // - right edge of adjacentRect is offscreen on the right
            //   * not clear what's the best behavior here - for now we'll position at the
            //     right edge of adjacentRect, even though that is offscreen, as we know we
            //     can scroll it into view.
            if (adjacentRect.left + adjacentRect.width < pageScrollLeft) {
                left = pageScrollLeft;
            } else {
                left = adjacentRect.left + adjacentRect.width;
            }
        } else {
            // Just slide into view on the page
            left = pageScrollLeft;
        }

    // jutting out to the right
    } else if (rightExcess > 0) {

        // if we're on the right edge, and can't occlude, jump over the adjacentRect and
        // put on the left edge (unless this would push it out of the viewport to the left)
        if (side == "right" && !canOcclude) {
            if ((adjacentRect.left - width) >= pageScrollLeft) {
                // if the adjacent rect is completely offscreen to the right, slide into view
                // on the right edge of the screen
                if (adjacentRect.left > (pageScrollLeft + pageWidth))
                    left = (pageScrollLeft + pageWidth) - width;
                else left = adjacentRect.left - width;
            }
            // If putting on the left edge would push the element out of the viewport on the
            // left, just leave on the right edge.
        } else {
            // If the object is wider than the page, just plonk it on the left edge of the
            // page (will continue to jut out to the right)
            // Otherwise align the right edge with the right edge of the page
            if (pageWidth < width) {
                left = pageScrollLeft;
            } else {
                left = pageScrollLeft + pageWidth - width;
            }
        }
    }

    // -- VERTICAL ADJUSTMENTS:
    // [see comments on horizontal adjustments - identical logic]
    // Clipped by top of viewport
    if (topExcess > 0) {
        if (side == "top" && !canOcclude) {
            if (adjacentRect.top + adjacentRect.height < pageScrollTop) {
                top = pageScrollTop;
            } else {
                top = adjacentRect.top + adjacentRect.height;
            }
        } else {
            // Just slide into view on the page
            top = pageScrollTop;
        }

    // clipped by bottom of viewport
    } else if (bottomExcess > 0) {

        if (side == "bottom" && !canOcclude) {
            if ((adjacentRect.top - height) >= pageScrollTop) {

                if (adjacentRect.top > (pageScrollTop + pageHeight))
                    top = (pageScrollTop + pageHeight) - height;
                else top = adjacentRect.top - height;
            }
            // If putting on the top edge would push the element out of the viewport on the
            // top, just leave on the bottom edge.
        } else {

            if (pageHeight < height) {
                top = pageScrollTop;
            } else {
                top = pageScrollTop + pageHeight - height;
            }
        }
    }
    return [left, top];

},





// clean up on unload
_handleUnload : function () {
    //>IE
    if (isc.Browser.isIE) this._clearDOMHandles(); //<IE

    var logViewer = isc.Log.logViewer;
    if (logViewer && logViewer.logWindowLoaded()) {
        logViewer._logWindow.openerUnloading();
        
        logViewer._logWindow = null;
    }
}

//>IE

,
_clearDOMHandles : function () {

    // get the list of global ID objects
    var list = this._canvasList;
    // now for each item that has a _handle, clear the pointers in both directions
    for (var i = 0; i < list.length; i++) {
        var canvas = list[i];
        // if the canvas exists...
        if (canvas) {
            // ...and has a handle, remove the references to and from the DOM
            if (canvas._handle) {
                // kill the reference from the DOM to JS
                canvas._handle.eventProxy = null;
                // kill the reference from JS to the DOM
                canvas._handle = null;
            }
        }
    }
    return true;
}
//<IE
,

//> @classMethod snapToEdge()
// consolidate logic for snapTo code. Aligns snapRect to targetRect base on parameters
// snapTo and snapEdge.
// @param targetRect - canvas to snap to, or array of coords [left, top, width, height]
// @param snapTo - edge against which to snap
// @param snapRect - canvas being snapped
// @param snapEdge - edge of snapRect to align with snapTo
//<

snapToEdge : function (targetRect, snapTo, snapRect, snapEdge, arbitraryCanvas) {

    // any combo of snapTo and snapEdge can be resolved by two fairly simple coordinate
    // transforms. SnapPoints are the 8 possible values for snapTo and snapEdge.
    // To get the final (top,left) of the canvas in question:
    //    1. find the coordinates of the snapPoint on this.parent/master given in this.snapTo
    //    2. map to the origin of this, starting from the snapPoint on this given in
    //      this.snapEdge.Use the coordinates from 1 as the location of this.snapEdge.
    //    3. move this to the resulting coordinates.

    // If we're snapping to an edge within our parent, use internal sizing
    // determine origin for first transform (inside borders, sb's etc).
    // Param targetRect can also be an array [left, top, width, height]
    var targetDims, insideCoords, targetOrigin;
    if (isc.isAn.Array(targetRect)) {
        insideCoords = false;
        targetOrigin = [targetRect[1], targetRect[0]];
        targetDims = [targetRect[2], targetRect[3]];
    } else if (snapRect.masterElement) {
        insideCoords = (snapRect.percentBox == snapRect._$viewport),
        targetDims = [insideCoords ? targetRect.getViewportWidth() :
                                      targetRect.getVisibleWidth(),
                       insideCoords ? targetRect.getViewportHeight() :
                                      targetRect.getVisibleHeight() ];
        targetOrigin = [targetRect.getTop() + (insideCoords ?
                                           (targetRect.getTopBorderSize() + targetRect.getTopMargin()) :
                                          0),
                        targetRect.getLeft() + (insideCoords ?
                                       (targetRect.getLeftBorderSize() + targetRect.getLeftMargin()) :
                                       0)
                        ];
    } else if (isc.isA.Canvas(arbitraryCanvas)) {
        insideCoords = (snapRect.percentBox == snapRect._$viewport),
        targetDims = [insideCoords ? arbitraryCanvas.getViewportWidth() :
                                      arbitraryCanvas.getVisibleWidth(),
                       insideCoords ? arbitraryCanvas.getViewportHeight() :
                                      arbitraryCanvas.getVisibleHeight() ];
        targetOrigin = [arbitraryCanvas.getPageTop() + (insideCoords ?
                            (arbitraryCanvas.getTopBorderSize() + arbitraryCanvas.getTopMargin()) :
                            0),
                        arbitraryCanvas.getPageLeft() + (insideCoords ?
                            (arbitraryCanvas.getLeftBorderSize() + arbitraryCanvas.getLeftMargin()) :
                            0)
                        ];
    } else {
        insideCoords = true;
        targetDims = [targetRect.getViewportWidth(), targetRect.getViewportHeight()];
        targetOrigin = [0, 0];
    }

    // get the coordinate on the target that we are snapping to
    var firstCoord = isc.Canvas._getSnapPoint(snapTo, targetOrigin, targetDims, false);
    // then modify this coordinate by our size, according to which of our edges should snap
    // to the target point
    var finalCoord = isc.Canvas._getSnapPoint((snapEdge || snapTo), firstCoord,
                                        [snapRect.getVisibleWidth(),snapRect.getVisibleHeight()], true);
    // note that _getSnapPoint() returns [top,left], not [left,top]
    if (snapRect.snapOffsetLeft != null) finalCoord[1] += snapRect.snapOffsetLeft;
    if (snapRect.snapOffsetTop != null) finalCoord[0] += snapRect.snapOffsetTop;

    // finally, move this to result coords
    snapRect.moveTo(finalCoord[1], finalCoord[0]);
    // let master know not to resize this peer
    snapRect._resizeWithMaster = false;
},

// give a rect (coord + size) return the coordinates in that rect that correspond to the edge.
_getSnapPoint : function (edge, coord, size, getInverse) {
    var cWidth = size[0],
        cHeight = size[1];

    // get the amount to add or subtract to top and left for each snap point
    var delta;
    if (edge == "TL") delta = [0, 0];
    else if (edge == "T") delta = [0, cWidth / 2];
    else if (edge == "TR") delta = [0, cWidth];
    else if (edge == "R") delta = [cHeight / 2, cWidth];
    else if (edge == "BR") delta = [cHeight, cWidth];
    else if (edge == "B") delta = [cHeight, cWidth / 2];
    else if (edge == "BL") delta = [cHeight, 0];
    else if (edge == "L") delta = [cHeight / 2, 0];
    else if (edge == "C") delta = [cHeight / 2, cWidth / 2];
    else delta = [0, 0];

    delta[0] = Math.floor(delta[0]);
    delta[1] = Math.floor(delta[1]);

    // apply the appropriate transform to the parameter coordinates
    if (getInverse) return [coord[0] - delta[0], coord[1] - delta[1]];
    else return [coord[0] + delta[0], coord[1] + delta[1]];
},


//  utility function to get the top level view
_getTopLevelWidget : function(globals) {

    if (!globals) { return null;}

    var globalKeys = isc.isAn.Array(globals) ? globals : isc.getKeys(globals)
    // find the last top-level Canvas in the globals and return it
    //
    // Note: globalEvalWithCapture return globalIDs in the order they were created.
    // Typically the top-level container is declared last since it incorporates other
    // Canvii declared before it, so we count down from the last created Canvas here.
    var _screen;    
    for (var i = 0; i < globalKeys.length; i++) {
        var global = globalKeys[i];
        var obj = window[global]; // globals are IDs, dereference

        if (obj && isc.isA.Canvas(obj) && 
            obj.parentElement == null && obj.masterElement == null) 
        {
            _screen = obj;
        }
    }
    return _screen;
},

// ------------------------
// ScreenReader method stubs


ariaEnabled : function () {
    return false;
},

useLiteAria : function () {
    return false;
}

});	// END isc.Canvas.addClassMethods()


//  'registerStringMethods()' - add all the instance properties that can be defined as strings
//  to evaluate (or as methods) to a central registry, together with their arguments as comma
//  separated strings.
//
isc.Canvas.registerStringMethods({
    // NOTE: event handlers are all legal to register as string methods.  We do this below.

    // Other legal stringMethods
    resized:"deltaX,deltaY", // note these args are intentionally not doc'd, but framework
                             // code in GR.addEmbeddedComponent() currently relies on them
    showIf:"canvas",
    childRemoved:"child,name",
    peerRemoved:"peer,name",
    deparented:"oldParent,name",
    depeered:"oldMaster,name",

    //> @method canvas.parentMoved()
    // Notification method fire when an ancestor of this component's position changes.
    // @param parent (canvas) the ancestor that moved
    // @param deltaX (int) horizontal difference between current and previous position
    // @param deltaY (int) vertical difference between current and previous position
    // @see canvas.moved()
    // @visibility external
    //<
    parentMoved:"parent,deltaX,deltaY",


    //> @method canvas.moved()
    // Notification method fired when this component is explicitly moved.
    // Note that a component's position on the screen may also changed due to an ancestor being
    // moved. The +link{parentMoved()} method provides a notification entry point to catch
    // that case as well.
    //
    // @param deltaX (int) horizontal difference between current and previous position
    // @param deltaY (int) vertical difference between current and previous position
    // @visibility external
    //<
    moved:"deltaX,deltaY",

    //> @method     canvas.focusChanged()
    // Notification function fired when this widget receives or loses keyboard focus.
    // @param   hasFocus (boolean) If true this widget now has keyboard focus
    // @group focus
    // @visibility external
    //<
    focusChanged:"hasFocus",

    //> @method canvas.scrolled()
    // Notification that this component has just scrolled.  Use with
    // +link{class.observe,observation}.
    // <P>
    // Fires for both CSS and +link{Scrollbar,"synthetic" scrollbars}.
    //
    // @group scrolling
    // @visibility external
    //<
    scrolled: "deltaX,deltaY",

    //> @method canvas.parentScrolled()
    // Notification that an ancestor of this component has just scrolled.  Use with
    // +link{class.observe,observation}.
    // <P>
    // Fires for both CSS and +link{Scrollbar,"synthetic" scrollbars}.
    //
    // @group scrolling
    //<
    parentScrolled: "parent,deltaX,deltaY",

    // The hover event is generated by the Canvas class, so not present in EH.eventTypes.
    hover:"",

    //> @method Canvas.onDrop()
    // Notification method fired when the user drops another canvas onto this one. Returning
    // <code>false</code> from this method will prevent any default drop behavior from occurring
    // @return (boolean) return false to cancel default drop handling
    // @visibility sgwt
    //<
    onDrop:"",

    //> @method canvas.visibilityChanged()
    // Notification  fired when this canvas becomes visible or hidden to the user.
    // Note - this method is fired when the +link{isVisible()} state of this
    // component changes. It may be fired in response an explicit call to +link{show()}
    // or +link{hide()} or +link{setVisibility()}, or in response to a parent component
    // being shown or hidden when this widgets +link{canvas.visibility} is set to "inherit".
    // <P>
    // Note that a call to +link{show()} or +link{hide()} will not <b>always</b> fire this
    // notification. If this widget has a hidden parent, show or hide would change this
    // components +link{canvas.visibility} property, and may update the CSS visibility attribute
    // of the drawn handle in the DOM, but would not actually hide or reveal the component to
    // the user and as such the notification would not fire.
    // <P>
    // Note also that this notification will only be fired for components which have been
    // +link{canvas.draw(),drawn}.
    // @param isVisible (boolean) whether the canvas is visible to the user
    // @visibility external
    //<
    visibilityChanged:"isVisible"


});

isc.Canvas._canvasInit = function () {
    var EH = isc.EH,
        noopHandlers = {};
    for (var eventName in EH.eventTypes) {
        // Register all events as string methods using the EventHandler's authoritative list
        this.registerStringMethods(EH.eventTypes[eventName], EH._eventHandlerArgString);

        // Make sure every event handler on all Canvas's has a NO-OP function as its default value,
        // so you don't get a JS error if you explicitly call (canvas.click()).
        var functionName = EH.eventTypes[eventName];
        if (this.getInstanceProperty(functionName) == null) {
            noopHandlers[functionName] = isc.Class.NO_OP;
        }
    }
    this.addMethods(noopHandlers);
}
isc.Canvas._canvasInit();



// Backmask
// ---------------------------------------------------------------------------------------
isc.defineClass("BackMask", "Canvas").addMethods({
    autoDraw:false,
    _isBackMask:true,
    _generated:true,
    
    useClipDiv: false,
    
    hideUsingDisplayNone: isc.Browser.isMoz,
	overflow:isc.Canvas.HIDDEN,
    contents:
     "<iframe width='100%' height='100%' border='0' frameborder='0' src=\"" +
        isc.Page.getBlankFrameURL() +

      "\" marginwidth='0' marginheight='0' scrolling='no' tabIndex='-1' tabStop='false'></iframe>",
    // custom sizing policy, to avoid the backmask "squaring-out" rounded corners.  Note
    // _sizeBackMask() currently both sizes and places BackMask, which prevent us using the
    // move-by-deltas approach of _moveWithMaster:true
    _moveWithMaster:false,
    masterMoved : function () { this.masterElement._sizeBackMask() },
    _resizeWithMaster:false,
    masterResized : function () { this.masterElement._sizeBackMask(); },
    
    draw : function (a,b,c) {
        // special suppressed flag - set by BrowserPlugin to suppress the backMask
        if (this.suppressed) return this;
        if (!this.readyToDraw()) return this;
        this.invokeSuper(isc.BackMask, this._$draw, a,b,c);
        if (this.masterElement.overflow == isc.Canvas.VISIBLE) this.masterElement._sizeBackMask();
        return this;
    },
    show : function () {
        // special suppressed flag - set by BrowserPlugin to suppress the backMask
        if (!this.suppressed) this.invokeSuper(isc.BackMask, "show");
    },

    _redrawWithMaster:false,
    _redrawWithParent:false
});

// ScreenSpan
// ---------------------------------------------------------------------------------------
isc.defineClass("ScreenSpan", "Canvas").addMethods({
    _generated:true,

    
    
    
    _spacerWidth:3200, _spacerHeight:2400,
    getInnerHTML:function () {
        if (!this._cachedContent) {
            // In IE7, the spacerHTML doesn't block clicks on "a href" links, but the img does.
            //
            // NOTE: if you update this code, also check and update EventHandler.makeEventMask();
            this._cachedContent = isc.Browser.isIE && isc.Browser.version > 6 ?
                isc.Canvas.imgHTML(this.src, this._spacerWidth,this._spacerHeight)
                : isc.Canvas.spacerHTML(this._spacerWidth,this._spacerHeight);
        }
        return this._cachedContent;
    },
    src:"[SKINIMG]/blank.gif",
    redrawOnResize:false,
    overflow:"hidden",

    
    hide : function (waited,b,c,d) {
        isc.ScreenSpan.removeFromSpanRegistry(this);
    
        this.resizeTo(1,1);
        this.moveTo(null,-this.getHeight());
        return this.invokeSuper(isc.ScreenSpan, "hide", waited,b,c,d);
    },

    show : function (a,b,c,d) {
        // This sets up the page resized observation so we continue to match the
        // screen size.
        isc.ScreenSpan.addToSpanRegistry(this);
        this.fitToScreen();
        
        return this.invokeSuper(isc.ScreenSpan, "show", a,b,c,d);
    },

    // DEBUG: set a translucent tint to see the screenSpan while debugging
    //backgroundColor:"blue",
	//opacity:30,

    fitToScreen : function () {
        
        var pageWidth = Math.max(isc.Page.getWidth(null, true), isc.Page.getScrollWidth()),
            pageHeight = Math.max(isc.Page.getHeight(null, true), isc.Page.getScrollHeight());

        

        this.resizeTo(pageWidth, pageHeight);
        // Ensure our actual content exceeds the scrollable area of the page so we don't
        // see a gap if the user scrolls down a long way!
        if (pageWidth > this._spacerWidth || pageHeight > this._spacerHeight) {
            this._spacerWidth = Math.max(pageWidth, this._spacerWidth);
            this._spacerHeight = Math.max(pageHeight, this._spacerHeight);
            delete this._cachedContent;
            this.markForRedraw("Resizing spacer HTML to fit large page content.");
        }

        this.moveTo(0,0);
    }
});

// Handle multiple screen spans showing at the same time.
// We have to handle these centrally as screen-spans need to fit the overflowed size of the
// page, so we'll have to shrink them all to a smaller size before we can calculate 
// what this desired size is
isc.ScreenSpan.addClassMethods({
    spanRegistry:{},
    addToSpanRegistry : function (span) {
        this.spanRegistry[span.getID()] = true;
        if (this.pageResizedEvent == null) {
            this.pageResizedEvent = isc.Page.setEvent("resize", this, isc.Page.FIRE_ONCE, 
                                        "pageResized");
        }
    },
    removeFromSpanRegistry : function (span) {
        delete this.spanRegistry[span.getID()];
        
        var clearResizedEvent = true;
        for (var i in this.spanRegistry) {
            if (this.spanRegistry[i]) {
                clearResizedEvent = false;
                break;
            }
        }
        if (this.pageResizedEvent != null && clearResizedEvent) {
            isc.Page.clearEvent("resize", this.pageResizedEvent);
            delete this.pageResizedEvent;
        }
    },
    pageResized : function () {
        var spans = [];
        for (var spanID in this.spanRegistry) {
            if (this.spanRegistry[spanID]) {
                var span = window[spanID];
                if (span && span.isVisible()) {
                    spans.add(span);

                    // resize to the browser viewport to avoid impacting the page
                    // scrollWidth/scrollHeight
                    span.resizeTo(isc.Page.getWidth(null, true), isc.Page.getHeight(null, true));
                }
            }
        }
        
        // fitToScreen will calculate page size, move to zero/zero and resize to cover the page.
        spans.map("fitToScreen");
 
        // ensure it resizes if the page is resized again.
        this.pageResizedEvent = isc.Page.setEvent(
            "resize",
            this,
            isc.Page.FIRE_ONCE,
            "pageResized"
        );
        
    }
});

// various methods to deal with forms - these are available as class and instance methods on
// Canvas
isc._formMethods = {
//>	@method	canvas.getForm()
//			get a form in this layer by name or number
//			returns null if form can't be found
//
//			NOTE: you're MUCH better off naming forms, since IE and Nav
//				set the form context very differently!
//		@group	form
//
//		@param	formID		(number or string)	name or index number of the form to get
//
//		@return	(object)	DOM form object if found, else null
//<
getForm : function (formID) {
	if (formID && typeof formID == "object") return formID;

    var theForm;
    if (formID != null && isc.Browser.isDOM) {
        // try looking up the form by ID attribute
        theForm = document.getElementById(formID);
    }
    if (theForm != null) return theForm;

    // try looking up the form via document.forms

    // default the formID parameter to the first form
    if (formID == null) formID = 0;

	// look for forms in the global document
	if (theForm == null) return document.forms[formID];
	return theForm;
},


//>	@method	canvas.getFormElementValue()
//			get a form element's value
//		does the right thing for text fields, checkboxes, radio button and selects
//		@group	form
//
//		@param	formID		(number or string)	name or index number of the form to get
//		@param	elementID	(number or string)	name or index number of the form element to get
//		@return				(any)		value of the element or null if form or element can't be found
//<
getFormElementValue : function (formID, elementID) {
	// get the form element -- if not found, bail
	var element = this.getFormElement(formID, elementID);
	if (!element) return;

	// now set the value according to the element type
	switch (element.type) {
	  case "radio":
		return (element.checked ? element.value : null)

	  case "checkbox":
		return element.checked;

	  case "select-one":
	  	if (!element.options || element.options.length == 0) return null;

		// get the option that's selected
		var option = element.options[element.selectedIndex];
		// if it has a value, return the value, otherwise return the name
		return option.value;//(option.value == null ? option.value : option.text);

	  case "select-multiple":
		var output = [];
		for (var i = 0, len = element.options.length; i < len; i++) {
			var option = element.options[i];
			if (option.selected)
				output.add(option.value);
		}
		return output;

	  case "button":
	  case "reset":
	  case "submit":
	  	return null;

	  default:
	  	// for text field, passwords, textAreas, etc. just return the value
		return element.value;
	}
},

//>	@method	canvas.getFormValues()
// Returns an object literal with formElement names to formElement values
// returns null if form can't be found
//
//		@group	form
//
//		@param	formID		(number or string)	name or index number of the form to get
//
//		@return	(object)	key/value pairs
//<
getFormValues : function (formID) {
	var theForm = this.getForm(formID);
	if (!theForm) return null;

	var formData = {};
    if(!theForm.elements) {
        this.logWarn("Form '"+formID+"' contains no elements - returning empty map for data.");
        return {};
    }
	for (var i = 0; i < theForm.elements.length; i++) {
		var formElement = theForm.elements[i];
		if (formElement.name != null) {
			var elementValue = this.getFormElementValue(theForm, theForm.elements[i]);
			if (elementValue != null) formData[formElement.name] = elementValue;
		}
	}
	return formData;
},



//>	@method	canvas.getFormElement()
//			get a form element by form name and element name
//		@group	form
//
//		@param	formID		(number or string or form)	name or index number of the form to get
//				returns null if form can't be found
//		@param	elementID		(number or string or form element)	name or index number of the form element to get
//		@return	(object)	form element object if found, else null
//<
getFormElement : function (formID, elementID) {
	// if they passed an element, just return it
	if (typeof elementID == "object") return elementID;
	// get the handle to the form
	var form = this.getForm(formID);
	// if the form was found, return the element if it can be found
	if (form) return form.elements[elementID];
	// otherwise return null
	return null;
}

};

isc.Canvas.addClassMethods(isc._formMethods);
isc.Canvas.addMethods(isc._formMethods);

//> @classMethod isc.setAutoDraw()
// Set the global default setting for +link{Canvas.autoDraw}.
// <p>
// After calling <code>isc.setAutoDraw()</code>, any newly created Canvas which is not given an
// explicit setting for +link{canvas.autoDraw,autoDraw} will follow the new default setting.
// <P>
// autoDraw:false is the recommended default setting for most applications since it ensures
// that extra draws will not occur when developers inadvertently omit the autoDraw:false
// setting on child components.
//
// @param [enable] whether autoDraw should be enabled or disabled.  Defaults to true.
// @see attr:Canvas.autoDraw
// @group autoDraw
// @visibility external
//<
isc.setAutoDraw = function (enable) {
    if (enable == null) enable = true;
    isc.Canvas.addProperties({
        autoDraw:enable
    });
};



isc.allowDuplicateStyles = true;

//	END package Canvas
//
////////////////////




//> @groupDef noFrames
// Loading the SmartClient framework into multiple frames or iframes within the same browser is
// not a supported configuration, or more accurately, not a <i>supportable</i> configuration,
// for the following reasons:
// <ul>
// <li> each additional frame multiplies the memory footprint and reduces speed
// <li> having multiple frames prevents drag and drop between components in different frames
// <li> modality handling (eg modal dialogs) doesn't automatically take into account multiple
// frames (consider tabbing order, nested modality and other issues, you'll see it's not
// realistic to provide automatic cross-frame modality handling)
// <li> inter-frame communication triggers several browser bugs: memory leaks, performance
// issues, intermittent crashes in some browsers, inconsistencies in basic JavaScript operators
// such as "typeof", and problems with form focus handling in IE, among many other bugs
// </ul>
// None of these problems are specific to SmartClient.  They happen with Ajax frameworks in
// general as well as other RIA technologies.  This is why no successful Ajax application has
// ever used the approach of double-loading a component framework into multiple frames.
// <P>
// The recommended +link{smartArchitecture,SmartClient Architecture} involves loading as many
// SmartClient-based application views as possible in the first page load, then showing and
// hiding different views as the user navigates through the application.
// <P>
// If, for whatever reason, you cannot follow the SmartClient Architecture and must load new
// SmartClient-based views by contacting the server each time, use the +link{ViewLoader} class
// to load new views, never frames.
// <P>
// Note that the use of IFrames is appropriate in certain circumstances, including loading
// certain types of content within an +link{HTMLFlow,contentsType,HTMLFlow}.  The only
// prohibited usage is loading the SmartClient framework into multiple frames within the same
// browser.
//
// @title Don't Misuse Frames
// @visibility external
//<

//> @type SCImgURL
// Properties that refer to images by URL, such as +link{Img.src} and +link{Button.icon}, are
// specially interpreted in SmartClient to allow for simpler and more uniform image URLs,
// and to allow applications to be restructured more easily.
// <P>
// <b>the application image directory</b>
// <P>
// When specifying URLs to image files via SmartClient component properties such as
// +link{StretchImg.src}, any relative path is assumed to be relative to the "application image
// directory" (<code>appImgDir</code>).  The application image directory can be set via
// +link{Page.setAppImgDir()}, and defaults to "images/", representing the typical practice of
// placing images in a subdirectory relative to the URL at which the application is accessed.
// <P>
// For applications that may be launched from multiple URLs, the <code>appImgDir</code> can be
// set to the correct relative path to the image directory by calling
// +link{Page.setAppImgDir()} before any SmartClient components are created.  This enables
// applications or components of an application to be launched from multiple locations, or to
// be relocated, without changing any image URLs supplied to SmartClient components.
// <P>
// <b>the "[SKIN]" URL prefix</b>
// <P>
// The special prefix "[SKIN]" can be used to refer to images within the skin folder
// whenever image URLs are supplied to SmartClient components.
// <P>
// The value of "[SKIN]" is the combination of:
// <ul>
// <li> the "skin directory", established in <code>load_skin.js</code> via +link{Page.setSkinDir()},
// plus..
// <li> the setting for +link{canvas.skinImgDir,skinImgDir} on the component where you set an
// image URL property
// </ul>
// <code>skinImgDir</code> defaults to "images/", so creating an +link{Img} component with
// +link{Img.src} set to "[SKIN]myButton/button.gif" will expand to <code>Page.getSkinDir() +
// "/images/myButton/button.gif"</code>.
// <P>
// Some components that use a large number of images use <code>skinImgDir</code> to group them
// together and make it possible to relocate all the media for the component with a single setting.
// For example, the +link{TreeGrid} class sets <code>skinImgDir</code> to "images/TreeGrid/".
// This allows +link{treeGrid.folderIcon} to be set to just "[SKIN]folder.gif" but refer to
// <code>Page.getSkinDir() + "/images/TreeGrid/folder.gif"</code>.
// <P>
// A custom subclass of TreeGrid can set <code>skinImgDir</code> to a different path, such as
// "/images/MyTreeGrid", to source all media from a different location.
// <P>
// TIPS:
// <ul>
// <li> subcomponents may not share the parent component's setting for skinImgDir.  For
// example, the +link{window.minimizeButton} has the default setting for "skinImgDir"
// ("images/"), so the +link{img.src,src} property used with this component is set to
// "[SKIN]/Window/minimize.png" (in the "SmartClient" sample skin).
// <li> for a particular image, the skinImgDir setting on the component may not be
// convenient.  The prefix "[SKINIMG]" can be used to refer to <code>Page.getSkinDir() +
// "/images"</code> regardless of the setting for <code>skinImgDir</code>
// </ul>
// <B>Stateful image URLs</B>
// <P>
// Many image URLs in SmartClient are "stateful", meaning that the actual URL used to fetch an
// image will vary according to the component's state (eg, "Disabled"), generally, by adding a
// suffix to the image URL.  See the +link{group:skinning,Skinning Overview} for more
// information on statefulness and the +link{Img.src} documentation for information on how
// stateful image URLs are formed.
//
// @visibility external
//<

//> @groupDef skinning
//
// Skinning (aka "theming" or "branding") is the process of modifying SmartClient's default
// look and feel to match the desired look and feel for your application.  SmartClient supports
// an extremely powerful and simple skinning system that allows designers with a basic grasp of
// CSS and JSON to skin any SmartClient component.
// <P>
// <h4>Basics</h4>
// <P>
// <ul>
// <li> SmartClient components create their visual appearance by dynamically generating HTML,
// within the browser, using JavaScript.
//
// <li> the HTML generated by SmartClient components contains CSS style names and URLs to
// images
//
// <li> SmartClient components can be skinned by replacing the CSS styles and images that
// the components use by default, or by using JavaScript properties to configure
// components to use new CSS styles and new image URLs.
//
// <li> You can change the appearance of an individual SmartClient component by
// <var class="smartclient">
// passing properties to +link{class.create,create()}, or you can skin all
// components of the same class at once, by using +link{classMethod:class.addProperties,addProperties()}
// and +link{class.changeDefaults,changeDefaults()} to change the defaults for the class.
// </var>
// <var class="smartgwt">
// calling setter methods such as +link{canvas.styleName,setStyleName()} or
// +link{canvas.backgroundColor,setBackgroundColor()}, or you can skin all
// components of the same class at once, by using Canvas.setDefaultProperties().
// to change the defaults for the class.
// </var>
//
// <li> A "skin" consists of:
// <ul>
// <li> a single CSS stylesheet containing all CSS styles used by SmartClient components
// (<code>skin_styles.css</code>)
// <li> a single JavaScript file that sets component defaults (<code>load_skin.js</code>)
// <li> a directory tree of images organized by component
// </ul>
//
// <li>
// The example skins that come with SmartClient are
// <var class="smartclient">in <code>smartclientSDK/isomorphic/skins</code>.</var>
// <var class="smartgwt">inside smartgwt.jar and smartgwt-skins.jar as GWT modules.</var>
// The standard directory layout for a skin is:
// <pre>
//        skin_styles.css
//        load_skin.js
//        images/
//            ListGrid/
//                sort_ascending.gif
//                ...
//            Tab/
//            ... other directories containing
//                component or shared media ...
// </pre>
// <li>
// <var class="smartclient">
// A skin is loaded via a &lt;SCRIPT SRC=&gt; tag that loads load_skin.js, or, if using
// the SmartClient server, by specifying the "skin" property of the +link{group:loadISCTag}.
// </var>
// <var class="smartgwt">
// A skin is implicitly loaded when you add an &lt;inherits&gt; tag in your .gwt.xml file to
// include SmartGWT components (name="com.smartgwt(ee).SmartGWT(Pro|Power|EE)").  To switch skins,
// add the "NoTheme" suffix to the "name" attribute of this &lt;inherits&gt; tag, then add
// &lt;inherits name="com.smartclient.theme.enterpriseblue.<i>SkinName</i>"/&gt;.  These
// tags cause a &lt;SCRIPT SRC=&gt; tag to be injected into your bootstrap .html page, which loads
// load_skin.js for the appropriate skin.
// </var>
// load_skin.js loads the stylesheet and sets the CSS styleNames and media URLs that
// SmartClient components will use.
// </ul>
// <P>
// <h4>CSS3 mode</h4>
// <P>
// Three of SmartClient's most commonly used skins - Enterprise, EnterpriseBlue and Graphite - now
// have a "CSS3 mode" in which almost all images required by the skin are replaced with CSS3
// settings that appear nearly identical to the image-based appearance.  This results in a
// performance boost through both a simplified DOM and far few images being loaded.
// <P>
// By default CSS3 mode is automatically used in modern browsers such as Firefox, Chrome, Safari,
// IE 9 in standards mode, and IE 10+.  Internet Explorer version 8 and earlier does not have sufficient
// CSS support to create a close match to the existing image-based skin, so CSS3 mode is not
// enabled by default. If CSS3 mode is manually enabled for IE when not automatically enabled,
// this will result in a degraded appearance that is similar across IE6, 7, and 8: rounded elements
// such as tabs will become square, and backgrounds will have lower quality if not disappear.
// <P>
// To override the default decision on whether to use CSS3 support, set the JavaScript global
// variable <code>isc_css3Mode</code> before any of the SmartClient libraries are loaded.  For
// example:
// <pre>
//   &lt;script&gt;isc_css3Mode = "on";&lt;/script&gt;
// </pre>
// <P>
// Possible settings are:
// <P>
// <ul>
//   <li>"supported" :<br><i>(default setting)</i> CSS3 mode will be used for browsers that fully
//       support it (including rounded edges and full gradient support)</li>
//   <li>"off" :<br>CSS3 mode will never be used</li>
//   <li>"on" :<br>CSS3 mode will be used for all browsers</li>
// </ul>
// <P>
// For more control than the above settings provide, you can create a custom skin based on one
// of the above 3 skins and modify load_skin.js - whether CSS3 mode is used is controlled by a
// JavaScript variable <code>useCSS3</code> defined in this file.
// <P>
// <b>NOTE</b>: we are working on improving IE9 support using the limited CSS3 features
// provided by this browser, but at the moment, the well-published workarounds for IE9's CSS3
// bugs create other bugs of their own that are, as yet, unresolved.
// <P>
// <h4>Spriting</h4>
// <p>
// In addition to having a CSS3 mode, the Enterprise, EnterpriseBlue, and Graphite skins also
// support spriting of user interface images. This typically results in reduced load times and
// eliminates noticeable delays in changes of component appearance while the browser downloads
// a required image. Because of these benefits, spriting is enabled by default in all browsers
// except for Internet&nbsp;Explorer 6.
// <p>
// When spriting is enabled, SmartClient uses images which have several of the smaller images
// combined into one. For example, the up and down arrow images of a +link{SpinnerItem} in
// the normal, "Focused", and "Disabled" states are combined into one image. The file size of
// the combined image is 60% smaller than the sum of the file sizes of the 6 constituent images,
// and the browser does not have to make 6 separate HTTP requests.
// <p>
// If certain component metrics (such as the height of a component or padding) are changed,
// then the Enterprise, EnterpriseBlue, and Graphite skins' image sprites might not work in the
// customized skin. In this case, spriting can be disabled by setting the JavaScript global
// variable <code>isc_spriting</code> to "off" before any of the SmartClient libraries are loaded.
// For example:
// <pre>
//   &lt;script&gt;isc_spriting = "off";&lt;/script&gt;
// </pre>
// Possible settings are:
// <P>
// <ul>
//   <li>"supported" :<br><i>(default setting)</i> Spriting will be used in browsers that fully
//       support it</li>
//   <li>"off" :<br>Spriting will not be used</li>
// </ul>
// <h4>Modifying Skins</h4>
// <P>
// To modify a skin, first create a copy of one of the skins that comes with the SmartClient
// SDK, then modify the copy.  Full instructions are provided in Chapter 9 of the
// <var class="smartclient">
// +docTreeLink{QuickStartGuide,QuickStart Guide}.
// </var>
// <var class="smartgwt">
// +externalLink{http://docs.smartclient.com,QuickStart Guide}.
// </var>
// <P>
// <h4>Locating Skinning Properties</h4>
// <P>
// <b>Starting from the name of the component</b>
// <P>
// <var class="smartclient">
// Given a SmartClient component that you want to skin, use the search feature of the SmartClient
// Reference to locate it, and open the "Instance APIs" tab:
// </var>
// <var class="smartgwt">
// Given a SmartClient component that you want to skin, open its JavaDoc:
// </var>
// <ul>
// <li> for properties that set CSS styles, look for properties whose name includes "style", eg
// +link{button.baseStyle}
// <li> for properties that control URLs to media, look for properties whose name includes
// "src", "image" or "icon", such as +link{Img.src}
// <li> for subcomponents that also support skinning,
// <var class="smartclient">
// look for properties of type "AutoChild" and check the reference for the type of the AutoChild for
// settable properties.  For example, +link{window.minimizeButton} is an ImgButton and therefore supports
// +link{imgButton.src}.
// </var>
// <var class="smartgwt">
// look for methods like set<i>Subcomponent</i>Properties(), such as Window.setPaneContainerProperties().
// </var>
// </ul>
// <var class="smartclient">
// <b>TIP</b>: the Instance APIs tab allows you to search within just the current class, limit
// the display to just properties or methods, and sort by type.
// </var>
// <P>
// <b>Starting from a running example</b>
// <P>
// <var class="smartclient">
// Open the Developer Console and use the Watch Tab to locate the component or subcomponent you
// want to skin, then locate it in the documentation, as above.
// <P>
// If you don't find the component in the documentation, it may be a custom component specific
// to your organization.  To find the base SmartClient component for a component named
// "MyComponent", use the following code to find out the name of the superclass:
// <pre>
//     isc.<i>MyComponent</i>.getSuperClass().getClassName()
// </pre>
// Repeat this until you arrive at a SmartClient built-in class.  You can execute this code in
// the "Eval JS" area of the Results pane of the Developer Console.
// </var>
// <P>
// Specific browsers offer alternate approaches to quickly discover the images or style names
// being used for a part of a SmartClient component's appearance:
// <ul>
// <li> the Firefox browser offers a dialog via Tools->"Page Info" that gives a manifest of
// media used in the page.
// <li> the +externalLink{http://www.getfirebug.com/,Firebug} extension for Firefox has an
// "Inspect" feature that allows you to see the HTML, CSS and media in use for a given area of
// the screen
// <li> right clicking (option-click on a Mac) on an image and choosing "Properties" shows a
// dialog that provides the image URL in most browsers.  Tips:
// <ul>
// <li> if a SmartClient component is showing text over an image, right-click at the very edge of
// the underlying image to get image properties rather than information about the text label
// <li> on some browsers, in order to see the full image URL, you may need to drag select the
// partial URL of the image shown in the properties dialog
// </ul>
// </ul>
// <P>
// <h4>Image URLs in SmartClient</h4>
// <P>
// Properties that refer to images by URL, such as +link{Img.src} and +link{Button.icon}, are
// specially interpreted in SmartClient to allow for simpler and more uniform image URLs,
// and to allow applications to be restructured more easily.
// <P>
// Unlike the URL used with an HTML &lt;IMG&gt; element, the image URL passed to a SmartClient
// component is not assumed to be relative to the current page.  See +link{type:SCImgURL} for a
// full explanation of the default application image directory, and the meaning of the "[SKIN]"
// prefix.
// <P>
// <h4>Specifying Image URLs</h4>
// <P>
// Default image URLs for SmartClient components are specified in <code>load_skin.js</code> via
// JavaScript, using calls to +link{classMethod:Class.addProperties()} and
// +link{class.changeDefaults()}.  For example, the <code>load_skin.js</code> file
// from the "Enterprise" skin includes the following code to establish the media used by
// +link{window.minimizeButton}:
// <pre>
//    isc.Window.changeDefaults("minimizeButtonDefaults", {
//         src:"[SKIN]/Window/minimize.png"
//    });
// </pre>
// <P>
// <var class="smartgwt"><b>NOTE:</b> These are JavaScript APIs and hence do not appear in SmartGWT
// JavaDoc - you may want to refer to the &#83;martClient Reference available at
// +externalLink{http://www.smartclient.com/product/documentation.jsp,Isomorphic.com} for these specific
// APIs.</var>
//
// <h4>Specifying Image Sizes</h4>
// <P>
// Many SmartClient components must know some image sizes in advance, in order to allow those
// components to autosize to data or content.
// <P>
// For example, the +link{ImgTab}s used in +link{TabSet}s are capable of automatically sizing
// to a variable length +link{tab.title}.  To make this possible, SmartClient must know the
// sizes of the images used as "endcaps" on each tab in advance.
// <P>
// Like image URLs, image sizes are specified in <code>load_skin.js</code>.  The following code
// sample establishes the default size of the "endcaps" for tabs, by setting a default value
// for +link{ImgTab.capSize}:
// <pre>
//     isc.ImgTab.addProperties({
//         capSize:4
//     })
// </pre>
// <P>
// <h4>CSS usage in SmartClient</h4>
// <P>
// In SmartClient, screen layout and sizing are controlled via JavaScript, and appearance via
// CSS and images.
// <P>
// CSS borders, margins and padding applied to SmartClient components can be treated as purely
// visual properties with no effect on sizing or layout.  Unlike HTML elements, a SmartClient
// component will always have the exact size you specify via JavaScript, regardless of browser
// platform, browser compatibility mode, or borders, margins, or padding, all of which normally
// affect the final size of an HTML element.
// <P>
// For this reason, SmartClient skinning requires only novice-level familiarity with CSS, as CSS
// is used principally for colors and fonts.  See +link{type:CSSStyleName,this discussion} for
// further details on what properties should be set via CSS vs via JavaScript.
// <P>
// <h4>Limitations</h4>
// <P>
// In most cases, using newer CSS features such as CSS3 prefix attribute selectors or CSS3
// pseudo-classes in a skin will just work, provided the browser supports those CSS features.
// However, in some cases, SmartClient needs to be able to extract style information from
// CSS style declarations. For this reason, only single class name selectors are officially
// supported (e.g. <code>.myButton, .myButtonDown</code>) and @-rules are not supported.
// <P>
// <h4>Statefulness and Suffixes</h4>
// <P>
// Some components or areas within components, including buttons and the cells within a grid, are
// "stateful", meaning that they can be in one of a set of states each of which has a distinct
// visual appearance.
// <P>
// Stateful components switch the CSS styles or image URLs they are using as they transition
// from state to state, appending state information as suffixes on the style names or URL.
// See +link{img.src} and +link{button.baseStyle} for details and examples.
// <P>
// SmartClient has built-in logic to manage a series of state transitions, such as:
// <ul>
// <li> "rollover": showing a different appearance when the mouse is over a component
// <li> "button down": showing a different appearance when the mouse is pressed over a
// component
// <li> "disabled": showing a different appearance when a component cannot be interacted with
// <li> "selected": showing one of a set of components in a different state to indicate
// selection
// </ul>
// Flags on some components, such as +link{ImgButton.showRollOver}, allow you to control whether the
// component will switch CSS style or image URL when the component transitions into a given state.
// <P>
// <h4>StretchImg: 3-segment stretchable images</h4>
// <P>
// A +link{StretchImg} is SmartClient component that renders out a compound image composed of 3
// image files: two fixed-size endcaps images and a stretchable center segment.  Like stateful
// components, the names of each image segment is appended to the image URL as a suffix.  See
// +link{stretchImg.src} for details.
// <P>
// <h4>EdgedCanvas</h4>
// <P>
// Similar to a StretchImg, an +link{EdgedCanvas} provides an image-based decorative edge
// around and/or behind another component, with up to 9 segments (a 3x3 grid).  Decorative
// edges can be added to any component by setting +link{canvas.showEdges,showEdges:true}.
// EdgedCanvas is also used to construct dropshadows, which can be enabled on any component via
// +link{canvas.showShadow,showShadow:true}.
// <P>
// <h4>Multiple looks for the same component type</h4>
// <P>
// In some cases you need to create two variations in appearance for a component with the same
// behavior.  For example, you may want to create a specialized Window, called "PaletteWindow",
// that behaves like a normal Window but has a very compact look & feel.  To create a
// separately skinnable component for PaletteWindow, use +link{classMethod:isc.defineClass()}.  For
// example:
// <pre>
//    isc.defineClass("PaletteWindow", "Window");
//    isc.PaletteWindow.addProperties({
//        showFooter:false,
//        ...
//    })
// </pre>
// <var class="smartgwt">This creates a &#83;martClient class, which does not exist as a Java class.  To
// make your SmartGWT widget use the settings on this &#83;martClient class, call setScClassName() passing
// the String name of the &#83;martClient class.
// <P>
// <h4>Where to put skin-related JavaScript</h4>
// <P>
// If you're creating a custom skin, you can place JavaScript snippets such as those shown above in your
// custom skin's load_skin.js file.  If you prefer not to create a custom skin for small customizations,
// you can execute JavaScript via a +externalLink{http://www.google.com/search?q=gwt+jsni,JSNI} method in
// your Java code.  With this latter approach, be sure to change "isc." to "$wnd.isc." wherever it
// appears, and to call the JSNI method before creating any SmartGWT components.
// </var>
//
// @treeLocation Concepts
// @visibility external
// @title Skinning / Theming
//<



