﻿/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
 */

		CKEDITOR.plugins.add('panel',
				{
					beforeInit: function (editor)
					{
						editor.ui.addHandler(CKEDITOR.UI_PANEL, CKEDITOR.ui.panel.handler);
					}
				});

/**
 * Panel UI element.
 * @constant
 * @example
 */
CKEDITOR.UI_PANEL = 2;

CKEDITOR.ui.panel = function (document, definition)
{
	// Copy all definition properties to this object.
	if (definition)
		CKEDITOR.tools.extend(this, definition);

	// Set defaults.
	CKEDITOR.tools.extend(this,
			{
				className: '',
				css: []
			});

	this.id = CKEDITOR.tools.getNextId();
	this.document = document;

	this._ =
			{
				blocks: {}
			};
};

/**
 * Transforms a rich combo definition in a {@link CKEDITOR.ui.richCombo}
 * instance.
 * @type Object
 * @example
 */
CKEDITOR.ui.panel.handler =
		{
			create: function (definition)
			{
				return new CKEDITOR.ui.panel(definition);
			}
		};

CKEDITOR.ui.panel.prototype =
		{
			renderHtml: function (editor)
			{
				var output = [];
				this.render(editor, output);
				return output.join('');
			},

			/**
			 * Renders the combo.
			 * @param {CKEDITOR.editor} editor The editor instance which this button is
			 *		to be used by.
			 * @param {Array} output The output array to which append the HTML relative
			 *		to this button.
			 * @example
			 */
			render: function (editor, output)
			{
				var id = this.id;

				output.push(
						'<div class="', editor.skinClass, '"' +
						' lang="', editor.langCode, '"' +
						' role="presentation"' +
						// iframe loading need sometime, keep the panel hidden(#4186).
						' style="display:none;z-index:' + (editor.config.baseFloatZIndex + 1) + '">' +
						'<div' +
						' id=', id,
						' dir=', editor.lang.dir,
						' role="presentation"' +
						' class="cke_panel cke_', editor.lang.dir);

				if (this.className)
					output.push(' ', this.className);

				output.push(
						'">');

				if (this.forceIFrame || this.css.length)
				{
					output.push(
							'<iframe id="', id, '_frame"' +
							' frameborder="0"' +
							' role="application" src="javascript:void(');

					output.push(
							// Support for custom document.domain in IE.
							CKEDITOR.env.isCustomDomain() ?
							'(function() {' +
							'document.open();' +
							'document.domain=\'' + document.domain + '\';' +
							'document.close();' +
							'})()'
							:
							'0');

					output.push(
							')"></iframe>');
				}

				output.push(
						'</div>' +
						'</div>');

				return id;
			},

			getHolderElement: function ()
			{
				var holder = this._.holder;

				if (!holder)
				{
					if (this.forceIFrame || this.css.length)
					{
						var iframe = this.document.getById(this.id + '_frame'),
								parentDiv = iframe.getParent(),
								dir = parentDiv.getAttribute('dir'),
								className = parentDiv.getParent().getAttribute('class'),
								langCode = parentDiv.getParent().getAttribute('lang'),
								doc = iframe.getFrameDocument();

						var onLoad = CKEDITOR.tools.addFunction(CKEDITOR.tools.bind(function (ev)
						{
							this.isLoaded = true;
							if (this.onLoad)
								this.onLoad();
						}, this));

						var data =
								'<!DOCTYPE html>' +
								'<html dir="' + dir + '" class="' + className + '_container" lang="' + langCode + '">' +
								'<head>' +
								'<style>.' + className + '_container{visibility:hidden}</style>' +
								'</head>' +
								'<body class="cke_' + dir + ' cke_panel_frame ' + CKEDITOR.env.cssClass + '" style="margin:0;padding:0"' +
								' onload="( window.CKEDITOR || window.parent.CKEDITOR ).tools.callFunction(' + onLoad + ');"></body>' +
								// It looks strange, but for FF2, the styles must go
								// after <body>, so it (body) becames immediatelly
								// available. (#3031)
								CKEDITOR.tools.buildStyleHtml(this.css) +
								'<\/html>';

						doc.write(data);

						var win = doc.getWindow();

						// Register the CKEDITOR global.
						win.$.CKEDITOR = CKEDITOR;

						// Arrow keys for scrolling is only preventable with 'keypress' event in Opera (#4534).
						doc.on('key' + (CKEDITOR.env.opera ? 'press' : 'down'), function (evt)
						{
							var keystroke = evt.data.getKeystroke(),
									dir = this.document.getById(this.id).getAttribute('dir');

							// Delegate key processing to block.
							if (this._.onKeyDown && this._.onKeyDown(keystroke) === false)
							{
								evt.data.preventDefault();
								return;
							}

							// ESC/ARROW-LEFT(ltr) OR ARROW-RIGHT(rtl)
							if (keystroke == 27 || keystroke == (dir == 'rtl' ? 39 : 37))
							{
								if (this.onEscape && this.onEscape(keystroke) === false)
									evt.data.preventDefault();
							}
						},
								this);

						holder = doc.getBody();
						holder.unselectable();
						CKEDITOR.env.air && CKEDITOR.tools.callFunction(onLoad);
					} else
						holder = this.document.getById(this.id);

					this._.holder = holder;
				}

				return holder;
			},

			addBlock: function (name, block)
			{
				block = this._.blocks[ name ] = block instanceof CKEDITOR.ui.panel.block ? block
						: new CKEDITOR.ui.panel.block(this.getHolderElement(), block);

				if (!this._.currentBlock)
					this.showBlock(name);

				return block;
			},

			getBlock: function (name)
			{
				return this._.blocks[ name ];
			},

			showBlock: function (name)
			{
				var blocks = this._.blocks,
						block = blocks[ name ],
						current = this._.currentBlock,
						holder = this.forceIFrame ?
						this.document.getById(this.id + '_frame')
						: this._.holder;

				// Disable context menu for block panel.
				holder.getParent().getParent().disableContextMenu();

				if (current)
				{
					// Clean up the current block's effects on holder.
					holder.removeAttributes(current.attributes);
					current.hide();
				}

				this._.currentBlock = block;

				holder.setAttributes(block.attributes);
				CKEDITOR.fire('ariaWidget', holder);

				// Reset the focus index, so it will always go into the first one.
				block._.focusIndex = -1;

				this._.onKeyDown = block.onKeyDown && CKEDITOR.tools.bind(block.onKeyDown, block);

				block.onMark = function (item)
				{
					holder.setAttribute('aria-activedescendant', item.getId() + '_option');
				};

				block.onUnmark = function ()
				{
					holder.removeAttribute('aria-activedescendant');
				};

				block.show();

				return block;
			},

			destroy: function ()
			{
				this.element && this.element.remove();
			}
		};

CKEDITOR.ui.panel.block = CKEDITOR.tools.createClass(
		{
			$: function (blockHolder, blockDefinition)
			{
				this.element = blockHolder.append(
						blockHolder.getDocument().createElement('div',
						{
							attributes:
									{
										'tabIndex': -1,
										'class': 'cke_panel_block',
										'role': 'presentation'
									},
							styles:
									{
										display: 'none'
									}
						}));

				// Copy all definition properties to this object.
				if (blockDefinition)
					CKEDITOR.tools.extend(this, blockDefinition);

				if (!this.attributes.title)
					this.attributes.title = this.attributes[ 'aria-label' ];

				this.keys = {};

				this._.focusIndex = -1;

				// Disable context menu for panels.
				this.element.disableContextMenu();
			},

			_: {

				/**
				 * Mark the item specified by the index as current activated.
				 */
				markItem: function (index)
				{
					if (index == -1)
						return;
					var links = this.element.getElementsByTag('a');
					var item = links.getItem(this._.focusIndex = index);

					// Safari need focus on the iframe window first(#3389), but we need
					// lock the blur to avoid hiding the panel.
					if (CKEDITOR.env.webkit || CKEDITOR.env.opera)
						item.getDocument().getWindow().focus();
					item.focus();

					this.onMark && this.onMark(item);
				}
			},

			proto:
					{
						show: function ()
						{
							this.element.setStyle('display', '');
						},

						hide: function ()
						{
							if (!this.onHide || this.onHide.call(this) !== true)
								this.element.setStyle('display', 'none');
						},

						onKeyDown: function (keystroke)
						{
							var keyAction = this.keys[ keystroke ];
							switch (keyAction)
							{
								// Move forward.
								case 'next' :
									var index = this._.focusIndex,
											links = this.element.getElementsByTag('a'),
											link;

									while ((link = links.getItem(++index)))
									{
										// Move the focus only if the element is marked with
										// the _cke_focus and it it's visible (check if it has
										// width).
										if (link.getAttribute('_cke_focus') && link.$.offsetWidth)
										{
											this._.focusIndex = index;
											link.focus();
											break;
										}
									}
									return false;

									// Move backward.
								case 'prev' :
									index = this._.focusIndex;
									links = this.element.getElementsByTag('a');

									while (index > 0 && (link = links.getItem(--index)))
									{
										// Move the focus only if the element is marked with
										// the _cke_focus and it it's visible (check if it has
										// width).
										if (link.getAttribute('_cke_focus') && link.$.offsetWidth)
										{
											this._.focusIndex = index;
											link.focus();
											break;
										}
									}
									return false;

								case 'click' :
									index = this._.focusIndex;
									link = index >= 0 && this.element.getElementsByTag('a').getItem(index);

									if (link)
										link.$.click ? link.$.click() : link.$.onclick();

									return false;
							}

							return true;
						}
					}
		});
