/**

 * $Id: Editor.js 965 2008-11-27 17:23:31Z spocke $

 *

 * @author Moxiecode

 * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved.

 */
(function() {
 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, Dispatcher = tinymce.util.Dispatcher;
 var each = tinymce.each, isGecko = tinymce.isGecko, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit;
 var is = tinymce.is, ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, EditorManager = tinymce.EditorManager;
 var inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
 /**#@+

	 * @class This class contains the core logic for a TinyMCE editor.

	 * @member tinymce.Editor

	 */
 tinymce.create('tinymce.Editor', {
  /**

		 * Constructs a editor instance by id.

		 *

		 * @constructor

		 * @member tinymce.Editor

		 * @param {String} id Unique id for the editor.

		 * @param {Object} s Optional settings string for the editor.

		 */
  Editor : function(id, s) {
   var t = this;
   t.id = t.editorId = id;
   t.execCommands = {};
   t.queryStateCommands = {};
   t.queryValueCommands = {};
   t.plugins = {};
   // Add events to the editor
   each([
    'onPreInit',
    'onBeforeRenderUI',
    'onPostRender',
    'onInit',
    'onRemove',
    'onActivate',
    'onDeactivate',
    'onClick',
    'onEvent',
    'onMouseUp',
    'onMouseDown',
    'onDblClick',
    'onKeyDown',
    'onKeyUp',
    'onKeyPress',
    'onContextMenu',
    'onSubmit',
    'onReset',
    'onPaste',
    'onPreProcess',
    'onPostProcess',
    'onBeforeSetContent',
    'onBeforeGetContent',
    'onSetContent',
    'onGetContent',
    'onLoadContent',
    'onSaveContent',
    'onNodeChange',
    'onChange',
    'onBeforeExecCommand',
    'onExecCommand',
    'onUndo',
    'onRedo',
    'onVisualAid',
    'onSetProgressState'
   ], function(e) {
    t[e] = new Dispatcher(t);
   });
   // Default editor config
   t.settings = s = extend({
    id : id,
    language : 'en',
    docs_language : 'en',
    theme : 'simple',
    skin : 'default',
    delta_width : 0,
    delta_height : 0,
    popup_css : '',
    plugins : '',
    document_base_url : tinymce.documentBaseURL,
    add_form_submit_trigger : 1,
    submit_patch : 1,
    add_unload_trigger : 1,
    convert_urls : 1,
    relative_urls : 1,
    remove_script_host : 1,
    table_inline_editing : 0,
    object_resizing : 1,
    cleanup : 1,
    accessibility_focus : 1,
    custom_shortcuts : 1,
    custom_undo_redo_keyboard_shortcuts : 1,
    custom_undo_redo_restore_selection : 1,
    custom_undo_redo : 1,
    doctype : '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">',
    visual_table_class : 'mceItemTable',
    visual : 1,
    inline_styles : true,
    convert_fonts_to_spans : true,
    font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
    apply_source_formatting : 1,
    directionality : 'ltr',
    forced_root_block : 'p',
    valid_elements : '@[id|class|style|title|dir<ltr?rtl|lang|xml::lang|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],a[rel|rev|charset|hreflang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur],strong/b,em/i,strike,u,#p[align],-ol[type|compact],-ul[type|compact],-li,br,img[longdesc|usemap|src|border|alt=|title|hspace|vspace|width|height|align],-sub,-sup,-blockquote[cite],-table[border=0|cellspacing|cellpadding|width|frame|rules|height|align|summary|bgcolor|background|bordercolor],-tr[rowspan|width|height|align|valign|bgcolor|background|bordercolor],tbody,thead,tfoot,#td[colspan|rowspan|width|height|align|valign|bgcolor|background|bordercolor|scope],#th[colspan|rowspan|width|height|align|valign|scope],caption,-div,-span,-code,-pre,address,-h1,-h2,-h3,-h4,-h5,-h6,hr[size|noshade],-font[face|size|color],dd,dl,dt,cite,abbr,acronym,del[datetime|cite],ins[datetime|cite],object[classid|width|height|codebase|*],param[name|value],embed[type|width|height|src|*],script[src|type],map[name],area[shape|coords|href|alt|target],bdo,button,col[align|char|charoff|span|valign|width],colgroup[align|char|charoff|span|valign|width],dfn,fieldset,form[action|accept|accept-charset|enctype|method],input[accept|alt|checked|disabled|maxlength|name|readonly|size|src|type|value|tabindex|accesskey],kbd,label[for],legend,noscript,optgroup[label|disabled],option[disabled|label|selected|value],q[cite],samp,select[disabled|multiple|name|size],small,textarea[cols|rows|disabled|name|readonly],tt,var,big',
    hidden_input : 1,
    padd_empty_editor : 1,
    render_ui : 1,
    init_theme : 1,
    force_p_newlines : 1,
    indentation : '30px',
    keep_styles : 1
   }, s);
   // Setup URIs
   t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
    base_uri : tinyMCE.baseURI
   });
   t.baseURI = EditorManager.baseURI;
   // Call setup
   t.execCallback('setup', t);
  },
  /**#@+

		 * @method

		 */
  /**

		 * Renderes the editor/adds it to the page.

		 */
  render : function(nst) {
   var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
   // Page is not loaded yet, wait for it
   if (!Event.domLoaded) {
    Event.add(document, 'init', function() {
     t.render();
    });
    return;
   }
   // Force strict loading mode if render us called by user and not internally
   if (!nst) {
    s.strict_loading_mode = 1;
    tinyMCE.settings = s;
   }
   // Element not found, then skip initialization
   if (!t.getElement())
    return;
   if (s.strict_loading_mode) {
    sl.settings.strict_mode = s.strict_loading_mode;
    tinymce.DOM.settings.strict = 1;
   }
   // Add hidden input for non input elements inside form elements
   if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
    DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
   t.windowManager = new tinymce.WindowManager(t);
   if (s.encoding == 'xml') {
    t.onGetContent.add(function(ed, o) {
     if (o.save)
      o.content = DOM.encode(o.content);
    });
   }
   if (s.add_form_submit_trigger) {
    t.onSubmit.addToTop(function() {
     if (t.initialized) {
      t.save();
      t.isNotDirty = 1;
     }
    });
   }
   if (s.add_unload_trigger && !s.ask) {
    t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
     if (t.initialized && !t.destroyed && !t.isHidden())
      t.save({format : 'raw', no_events : true});
    });
   }
   tinymce.addUnload(t.destroy, t);

   if (s.submit_patch) {
    t.onBeforeRenderUI.add(function() {
     var n = t.getElement().form;

     if (!n)
      return;

     // Already patched
     if (n._mceOldSubmit)
      return;

     // Check page uses id="submit" or name="submit" for it's submit button
     if (!n.submit.nodeType && !n.submit.length) {
      t.formElement = n;
      n._mceOldSubmit = n.submit;
      n.submit = function() {
       // Save all instances
       EditorManager.triggerSave();
       t.isNotDirty = 1;

       return this._mceOldSubmit(this);
      };
     }

     n = null;
    });
   }

   // Load scripts
   function loadScripts() {
    if (s.language)
     sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');

    if (s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
     ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');

    each(explode(s.plugins), function(p) {
     if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
      // Skip safari plugin for other browsers
      if (!isWebKit && p == 'safari')
       return;

      PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
     }
    });

    // Init when que is loaded
    sl.loadQueue(function() {
     if (s.ask) {
      function ask() {
       // Yield for awhile to avoid focus bug on FF 3 when cancel is pressed
       window.setTimeout(function() {
        Event.remove(t.id, 'focus', ask);

        t.windowManager.confirm(t.getLang('edit_confirm'), function(s) {
         if (s)
          t.init();
        });
       }, 0);
      };

      Event.add(t.id, 'focus', ask);
      return;
     }

     if (!t.removed)
      t.init();
    });
   };

   // Load compat2x first
   if (s.plugins.indexOf('compat2x') != -1) {
    PluginManager.load('compat2x', 'plugins/compat2x/editor_plugin' + tinymce.suffix + '.js');
    sl.loadQueue(loadScripts);
   } else
    loadScripts();
  },

  /**

		 * Initializes the editor this will be called automatically when

		 * all plugins/themes and language packs are loaded by the rendered method.

		 * This method will setup the iframe and create the theme and plugin instances.

		 */
  init : function() {
   var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re;
   EditorManager.add(t);
   // Create theme
   s.theme = s.theme.replace(/-/, '');
   o = ThemeManager.get(s.theme);
   t.theme = new o();
   if (t.theme.init && s.init_theme)
    t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
   // Create all plugins
   each(explode(s.plugins.replace(/\-/g, '')), function(p) {
    var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;

    if (c) {
     po = new c(t, u);

     t.plugins[p] = po;

     if (po.init)
      po.init(t, u);
    }
   });

   // Setup popup CSS path(s)
   if (s.popup_css !== false) {
    if (s.popup_css)
     s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
    else
     s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
   }

   if (s.popup_css_add)
    s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);

   // Setup control factory
   t.controlManager = new tinymce.ControlManager(t);
   t.undoManager = new tinymce.UndoManager(t);

   // Pass through
   t.undoManager.onAdd.add(function(um, l) {
    if (!l.initial)
     return t.onChange.dispatch(t, l, um);
   });

   t.undoManager.onUndo.add(function(um, l) {
    return t.onUndo.dispatch(t, l, um);
   });

   t.undoManager.onRedo.add(function(um, l) {
    return t.onRedo.dispatch(t, l, um);
   });

   if (s.custom_undo_redo) {
    t.onExecCommand.add(function(ed, cmd, ui, val, a) {
     if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
      t.undoManager.add();
    });
   }

   t.onExecCommand.add(function(ed, c) {
    // Don't refresh the select lists until caret move
    if (!/^(FontName|FontSize)$/.test(c))
     t.nodeChanged();
   });

   // Remove ghost selections on images and tables in Gecko
   if (isGecko) {
    function repaint(a, o) {
     if (!o || !o.initial)
      t.execCommand('mceRepaint');
    };

    t.onUndo.add(repaint);
    t.onRedo.add(repaint);
    t.onSetContent.add(repaint);
   }

   // Enables users to override the control factory
   t.onBeforeRenderUI.dispatch(t, t.controlManager);

   // Measure box
   if (s.render_ui) {
    w = s.width || e.style.width || e.offsetWidth;
    h = s.height || e.style.height || e.offsetHeight;
    t.orgDisplay = e.style.display;
    re = /^[0-9\.]+(|px)$/i;

    if (re.test('' + w))
     w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);

    if (re.test('' + h))
     h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);

    // Render UI
    o = t.theme.renderUI({
     targetNode : e,
     width : w,
     height : h,
     deltaWidth : s.delta_width,
     deltaHeight : s.delta_height
    });

    t.editorContainer = o.editorContainer;
   }

   // #if contentEditable

   // Content editable mode ends here
   if (s.content_editable) {
    e = n = o = null; // Fix IE leak
    return t.setupContentEditable();
   }

   // #endif

   // Resize editor
   DOM.setStyles(o.sizeContainer || o.editorContainer, {
    width : w,
    height : h
   });

   h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
   if (h < 100)
    h = 100;

   t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml"><base href="' + t.documentBaseURI.getURI() + '" />';
   t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';

   if (tinymce.relaxedDomain)
    t.iframeHTML += '<script type="text/javascript">document.domain = "' + tinymce.relaxedDomain + '";</script>';

   bi = s.body_id || 'tinymce';
   if (bi.indexOf('=') != -1) {
    bi = t.getParam('body_id', '', 'hash');
    bi = bi[t.id] || bi;
   }

   bc = s.body_class || '';
   if (bc.indexOf('=') != -1) {
    bc = t.getParam('body_class', '', 'hash');
    bc = bc[t.id] || '';
   }

   t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';

   // Domain relaxing enabled, then set document domain
   if (tinymce.relaxedDomain) {
    // We need to write the contents here in IE since multiple writes messes up refresh button and back button
    if (isIE || (tinymce.isOpera && parseFloat(opera.version()) >= 9.5))
     u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()';
    else if (tinymce.isOpera)
     u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";document.close();ed.setupIframe();})()';
   }

   // Create iframe
   n = DOM.add(o.iframeContainer, 'iframe', {
    id : t.id + "_ifr",
    src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
    frameBorder : '0',
    style : {
     width : '100%',
     height : h
    }
   });

   t.contentAreaContainer = o.iframeContainer;
   DOM.get(o.editorContainer).style.display = t.orgDisplay;
   DOM.get(t.id).style.display = 'none';

   // Safari 2.x requires us to wait for the load event and load a real HTML doc
   if (tinymce.isOldWebKit) {
    Event.add(n, 'load', t.setupIframe, t);
    n.src = tinymce.baseURL + '/plugins/safari/blank.htm';
   } else {
    if (!isIE || !tinymce.relaxedDomain)
     t.setupIframe();

    e = n = o = null; // Cleanup
   }
  },

  /**

		 * This method get called by the init method ones the iframe is loaded.

		 * It will fill the iframe with contents, setups DOM and selection objects for the iframe.

		 * This method should not be called directly.

		 */
  setupIframe : function() {
   var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
   // Setup iframe body
   if (!isIE || !tinymce.relaxedDomain) {
    d.open();
    d.write(t.iframeHTML);
    d.close();
   }
   // Design mode needs to be added here Ctrl+A will fail otherwise
   if (!isIE) {
    try {
     if (!s.readonly)
      d.designMode = 'On';
    } catch (ex) {
     // Will fail on Gecko if the editor is placed in an hidden container element
     // The design mode will be set ones the editor is focused
    }
   }
   // IE needs to use contentEditable or it will display non secure items for HTTPS
   if (isIE) {
    // It will not steal focus if we hide it while setting contentEditable
    b = t.getBody();
    DOM.hide(b);
    if (!s.readonly)
     b.contentEditable = true;

    DOM.show(b);
   }

   // Setup objects
   t.dom = new tinymce.DOM.DOMUtils(t.getDoc(), {
    keep_values : true,
    url_converter : t.convertURL,
    url_converter_scope : t,
    hex_colors : s.force_hex_style_colors,
    class_filter : s.class_filter,
    update_styles : 1,
    fix_ie_paragraphs : 1
   });

   t.serializer = new tinymce.dom.Serializer({
    entity_encoding : s.entity_encoding,
    entities : s.entities,
    valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements,
    extended_valid_elements : s.extended_valid_elements,
    valid_child_elements : s.valid_child_elements,
    invalid_elements : s.invalid_elements,
    fix_table_elements : s.fix_table_elements,
    fix_list_elements : s.fix_list_elements,
    fix_content_duplication : s.fix_content_duplication,
    convert_fonts_to_spans : s.convert_fonts_to_spans,
    font_size_classes : s.font_size_classes,
    font_size_style_values : s.font_size_style_values,
    apply_source_formatting : s.apply_source_formatting,
    remove_linebreaks : s.remove_linebreaks,
    element_format : s.element_format,
    dom : t.dom
   });

   t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
   t.forceBlocks = new tinymce.ForceBlocks(t, {
    forced_root_block : s.forced_root_block
   });
   t.editorCommands = new tinymce.EditorCommands(t);

   // Pass through
   t.serializer.onPreProcess.add(function(se, o) {
    return t.onPreProcess.dispatch(t, o, se);
   });

   t.serializer.onPostProcess.add(function(se, o) {
    return t.onPostProcess.dispatch(t, o, se);
   });

   t.onPreInit.dispatch(t);

   if (!s.gecko_spellcheck)
    t.getBody().spellcheck = 0;

   if (!s.readonly)
    t._addEvents();

   t.controlManager.onPostRender.dispatch(t, t.controlManager);
   t.onPostRender.dispatch(t);

   if (s.directionality)
    t.getBody().dir = s.directionality;

   if (s.nowrap)
    t.getBody().style.whiteSpace = "nowrap";

   if (s.auto_resize)
    t.onNodeChange.add(t.resizeToContent, t);

   if (s.custom_elements) {
    function handleCustom(ed, o) {
     each(explode(s.custom_elements), function(v) {
      var n;

      if (v.indexOf('~') === 0) {
       v = v.substring(1);
       n = 'span';
      } else
       n = 'div';

      o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' mce_name="$1"$2>');
      o.content = o.content.replace(new RegExp('</(' + v + ')>', 'g'), '</' + n + '>');
     });
    };

    t.onBeforeSetContent.add(handleCustom);
    t.onPostProcess.add(function(ed, o) {
     if (o.set)
      handleCustom(ed, o)
    });
   }

   if (s.handle_node_change_callback) {
    t.onNodeChange.add(function(ed, cm, n) {
     t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
    });
   }

   if (s.save_callback) {
    t.onSaveContent.add(function(ed, o) {
     var h = t.execCallback('save_callback', t.id, o.content, t.getBody());

     if (h)
      o.content = h;
    });
   }

   if (s.onchange_callback) {
    t.onChange.add(function(ed, l) {
     t.execCallback('onchange_callback', t, l);
    });
   }

   if (s.convert_newlines_to_brs) {
    t.onBeforeSetContent.add(function(ed, o) {
     if (o.initial)
      o.content = o.content.replace(/\r?\n/g, '<br />');
    });
   }

   if (s.fix_nesting && isIE) {
    t.onBeforeSetContent.add(function(ed, o) {
     o.content = t._fixNesting(o.content);
    });
   }

   if (s.preformatted) {
    t.onPostProcess.add(function(ed, o) {
     o.content = o.content.replace(/^\s*<pre.*?>/, '');
     o.content = o.content.replace(/<\/pre>\s*$/, '');

     if (o.set)
      o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
    });
   }

   if (s.verify_css_classes) {
    t.serializer.attribValueFilter = function(n, v) {
     var s, cl;

     if (n == 'class') {
      // Build regexp for classes
      if (!t.classesRE) {
       cl = t.dom.getClasses();

       if (cl.length > 0) {
        s = '';

        each (cl, function(o) {
         s += (s ? '|' : '') + o['class'];
        });

        t.classesRE = new RegExp('(' + s + ')', 'gi');
       }
      }

      return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
     }

     return v;
    };
   }

   if (s.convert_fonts_to_spans)
    t._convertFonts();

   if (s.inline_styles)
    t._convertInlineElements();

   if (s.cleanup_callback) {
    t.onBeforeSetContent.add(function(ed, o) {
     o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
    });

    t.onPreProcess.add(function(ed, o) {
     if (o.set)
      t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);

     if (o.get)
      t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
    });

    t.onPostProcess.add(function(ed, o) {
     if (o.set)
      o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);

     if (o.get)
      o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
    });
   }

   if (s.save_callback) {
    t.onGetContent.add(function(ed, o) {
     if (o.save)
      o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
    });
   }

   if (s.handle_event_callback) {
    t.onEvent.add(function(ed, e, o) {
     if (t.execCallback('handle_event_callback', e, ed, o) === false)
      Event.cancel(e);
    });
   }

   t.onSetContent.add(function() {
    // Safari needs some time, it will crash the browser when a link is created otherwise
    // I think this crash issue is resolved in the latest 3.0.4
    //window.setTimeout(function() {
     t.addVisual(t.getBody());
    //}, 1);
   });

   // Remove empty contents
   if (s.padd_empty_editor) {
    t.onPostProcess.add(function(ed, o) {
     o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
    });
   }

   if (isGecko && !s.readonly) {
    try {
     // Design mode must be set here once again to fix a bug where
     // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
     d.designMode = 'Off';
     d.designMode = 'On';
    } catch (ex) {
     // Will fail on Gecko if the editor is placed in an hidden container element
     // The design mode will be set ones the editor is focused
    }
   }

   // A small timeout was needed since firefox will remove. Bug: #1838304
   setTimeout(function () {
    if (t.removed)
     return;

    t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')});
    t.startContent = t.getContent({format : 'raw'});
    t.undoManager.add({initial : true});
    t.initialized = true;

    t.onInit.dispatch(t);
    t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
    t.execCallback('init_instance_callback', t);
    t.focus(true);
    t.nodeChanged({initial : 1});

    // Load specified content CSS last
    if (s.content_css) {
     tinymce.each(explode(s.content_css), function(u) {
      t.dom.loadCSS(t.documentBaseURI.toAbsolute(u));
     });
    }

    // Handle auto focus
    if (s.auto_focus) {
     setTimeout(function () {
      var ed = EditorManager.get(s.auto_focus);

      ed.selection.select(ed.getBody(), 1);
      ed.selection.collapse(1);
      ed.getWin().focus();
     }, 100);
    }
   }, 1);

   e = null;
  },

  // #if contentEditable

  /**

		 * Sets up the contentEditable mode.

		 */
  setupContentEditable : function() {
   var t = this, s = t.settings, e = t.getElement();
   t.contentDocument = s.content_document || document;
   t.contentWindow = s.content_window || window;
   t.bodyElement = e;
   // Prevent leak in IE
   s.content_document = s.content_window = null;

   DOM.hide(e);
   e.contentEditable = true;
   DOM.show(e);

   if (!s.gecko_spellcheck)
    t.getDoc().body.spellcheck = 0;

   // Setup objects
   t.dom = new tinymce.DOM.DOMUtils(t.getDoc(), {
    keep_values : true,
    url_converter : t.convertURL,
    url_converter_scope : t,
    hex_colors : s.force_hex_style_colors,
    class_filter : s.class_filter,
    root_element : t.id,
    strict_root : 1,
    fix_ie_paragraphs : 1,
    update_styles : 1
   });

   t.serializer = new tinymce.dom.Serializer({
    entity_encoding : s.entity_encoding,
    entities : s.entities,
    valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements,
    extended_valid_elements : s.extended_valid_elements,
    valid_child_elements : s.valid_child_elements,
    invalid_elements : s.invalid_elements,
    fix_table_elements : s.fix_table_elements,
    fix_list_elements : s.fix_list_elements,
    fix_content_duplication : s.fix_content_duplication,
    convert_fonts_to_spans : s.convert_fonts_to_spans,
    font_size_classes : s.font_size_classes,
    font_size_style_values : s.font_size_style_values,
    apply_source_formatting : s.apply_source_formatting,
    dom : t.dom
   });

   t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
   t.forceBlocks = new tinymce.ForceBlocks(t, {
    forced_root_block : s.forced_root_block
   });
   t.editorCommands = new tinymce.EditorCommands(t);

   // Pass through
   t.serializer.onPreProcess.add(function(se, o) {
    return t.onPreProcess.dispatch(t, o, se);
   });

   t.serializer.onPostProcess.add(function(se, o) {
    return t.onPostProcess.dispatch(t, o, se);
   });

   t.onPreInit.dispatch(t);
   t._addEvents();

   t.controlManager.onPostRender.dispatch(t, t.controlManager);
   t.onPostRender.dispatch(t);

   if (s.convert_fonts_to_spans)
    t._convertFonts();

   if (s.inline_styles)
    t._convertInlineElements();

   t.onSetContent.add(function() {
    t.addVisual(t.getBody());
   });

   t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')});
   t.startContent = t.getContent({format : 'raw'});
   t.undoManager.add({initial : true});
   t.initialized = true;

   t.onInit.dispatch(t);
   t.focus(true);
   t.nodeChanged({initial : 1});

   // Load specified content CSS last
   if (s.content_css) {
    each(explode(s.content_css), function(u) {
     t.dom.loadCSS(t.documentBaseURI.toAbsolute(u));
    });
   }

   if (isIE) {
    t.onBeforeExecCommand.add(function(ed, cmd, ui, val, o) {
     if (!DOM.getParent(ed.selection.getStart(), function(n) {return n == ed.getBody();}))
      o.terminate = 1;

     if (!DOM.getParent(ed.selection.getEnd(), function(n) {return n == ed.getBody();}))
      o.terminate = 1;
    });
   }

   e = null; // Cleanup
  },

  // #endif

  /**

		 * Focuses/activates the editor. This will set this editor as the activeEditor in the EditorManager

		 * it will also place DOM focus inside the editor.

		 *

		 * @param {bool} sf Skip DOM focus. Just set is as the active editor.

		 */
  focus : function(sf) {
   var oed, t = this, ce = t.settings.content_editable;
   if (!sf) {
    // Is not content editable or the selection is outside the area in IE
    // the IE statement is needed to avoid bluring if element selections inside layers since
    // the layer is like it's own document in IE
    if (!ce && (!isIE || t.selection.getNode().ownerDocument != t.getDoc()))
     t.getWin().focus();
    // #if contentEditable
    // Content editable mode ends here
    if (tinymce.isIE && ce)
     t.getElement().focus();
    // #endif
   }
   if (EditorManager.activeEditor != t) {
    if ((oed = EditorManager.activeEditor) != null)
     oed.onDeactivate.dispatch(oed, t);

    t.onActivate.dispatch(t, oed);
   }

   EditorManager._setActive(t);
  },

  /**

		 * Executes a legacy callback. This method is useful to call old 2.x option callbacks.

		 * There new event model is a better way to add callback so this method might be removed in the future.

		 *

		 * @param {String} n Name of the callback to execute.

		 * @return {Object} Return value passed from callback function.

		 */
  execCallback : function(n) {
   var t = this, f = t.settings[n], s;
   if (!f)
    return;
   // Look through lookup
   if (t.callbackLookup && (s = t.callbackLookup[n])) {
    f = s.func;
    s = s.scope;
   }
   if (is(f, 'string')) {
    s = f.replace(/\.\w+$/, '');
    s = s ? tinymce.resolve(s) : 0;
    f = tinymce.resolve(f);
    t.callbackLookup = t.callbackLookup || {};
    t.callbackLookup[n] = {func : f, scope : s};
   }
   return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
  },
  /**

		 * Translates the specified string by replacing variables with language pack items it will also check if there is

		 * a key mathcin the input.

		 *

		 * @param {String} s String to translate by the language pack data.

		 * @return {String} Translated string.

		 */
  translate : function(s) {
   var c = this.settings.language || 'en', i18n = EditorManager.i18n;
   if (!s)
    return '';
   return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
    return i18n[c + '.' + b] || '{#' + b + '}';
   });
  },
  /**

		 * Returns a language pack item by name/key.

		 *

		 * @param {String} n Name/key to get from the language pack.

		 * @param {String} dv Optional default value to retrive.

		 */
  getLang : function(n, dv) {
   return EditorManager.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
  },
  /**

		 * Returns a configuration parameter by name.

		 *

		 * @param {String} n Configruation parameter to retrive.

		 * @param {String} dv Optional default value to return.

		 * @param {String} ty Optional type parameter.

		 * @return {String} Configuration parameter value or default value.

		 */
  getParam : function(n, dv, ty) {
   var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
   if (ty === 'hash') {
    o = {};
    if (is(v, 'string')) {
     each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
      v = v.split('=');
      if (v.length > 1)
       o[tr(v[0])] = tr(v[1]);
      else
       o[tr(v[0])] = tr(v);
     });
    } else
     o = v;
    return o;
   }
   return v;
  },
  /**

		 * Distpaches out a onNodeChange event to all observers. This method should be called when you

		 * need to update the UI states or element path etc.

		 *

		 * @param {Object} o Optional object to pass along for the node changed event.

		 */
  nodeChanged : function(o) {
   var t = this, s = t.selection, n = s.getNode() || t.getBody();
   // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
   if (t.initialized) {
    t.onNodeChange.dispatch(
     t,
     o ? o.controlManager || t.controlManager : t.controlManager,
     isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n, // Fix for IE initial state
     s.isCollapsed(),
     o
    );
   }
  },
  /**

		 * Adds a button that later gets created by the ControlManager. This is a shorter and easier method

		 * of adding buttons without the need to deal with the ControlManager directly. But it's also less

		 * powerfull if you need more control use the ControlManagers factory methods instead.

		 *

		 * @param {String} n Button name to add.

		 * @param {Object} s Settings object with title, cmd etc.

		 */
  addButton : function(n, s) {
   var t = this;
   t.buttons = t.buttons || {};
   t.buttons[n] = s;
  },
  /**

		 * Adds a custom command to the editor, you can also override existing commands with this method.

		 * The command that you add can be executed with execCommand.

		 *

		 * @param {String} n Command name to add/override.

		 * @param {function} f Function to execute when the command occurs.

		 * @param {Object} s Optional scope to execute the function in.

		 */
  addCommand : function(n, f, s) {
   this.execCommands[n] = {func : f, scope : s || this};
  },
  /**

		 * Adds a custom query state command to the editor, you can also override existing commands with this method.

		 * The command that you add can be executed with queryCommandState function.

		 *

		 * @param {String} n Command name to add/override.

		 * @param {function} f Function to execute when the command state retrival occurs.

		 * @param {Object} s Optional scope to execute the function in.

		 */
  addQueryStateHandler : function(n, f, s) {
   this.queryStateCommands[n] = {func : f, scope : s || this};
  },
  /**

		 * Adds a custom query value command to the editor, you can also override existing commands with this method.

		 * The command that you add can be executed with queryCommandValue function.

		 *

		 * @param {String} n Command name to add/override.

		 * @param {function} f Function to execute when the command value retrival occurs.

		 * @param {Object} s Optional scope to execute the function in.

		 */
  addQueryValueHandler : function(n, f, s) {
   this.queryValueCommands[n] = {func : f, scope : s || this};
  },
  /**

		 * Adds a keyboard shortcut for some command or function.

		 *

		 * @param {String} pa Shortcut pattern. Like for example: ctrl+alt+o.

		 * @param {String} desc Text description for the command.

		 * @param {String/Function} cmd_func Command name string or function to execute when the key is pressed.

		 * @param {Object} sc Optional scope to execute the function in.

		 * @return {bool} true/false state if the shortcut was added or not.

		 */
  addShortcut : function(pa, desc, cmd_func, sc) {
   var t = this, c;
   if (!t.settings.custom_shortcuts)
    return false;
   t.shortcuts = t.shortcuts || {};
   if (is(cmd_func, 'string')) {
    c = cmd_func;
    cmd_func = function() {
     t.execCommand(c, false, null);
    };
   }
   if (is(cmd_func, 'object')) {
    c = cmd_func;
    cmd_func = function() {
     t.execCommand(c[0], c[1], c[2]);
    };
   }
   each(explode(pa), function(pa) {
    var o = {
     func : cmd_func,
     scope : sc || this,
     desc : desc,
     alt : false,
     ctrl : false,
     shift : false
    };
    each(explode(pa, '+'), function(v) {
     switch (v) {
      case 'alt':
      case 'ctrl':
      case 'shift':
       o[v] = true;
       break;
      default:
       o.charCode = v.charCodeAt(0);
       o.keyCode = v.toUpperCase().charCodeAt(0);
     }
    });
    t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
   });
   return true;
  },
  /**

		 * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or

		 * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org.

		 * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these

		 * return true it will handle the command as a internal browser command.

		 *

		 * @param {String} cmd Command name to execute, for example mceLink or Bold.

		 * @param {bool} ui True/false state if a UI (dialog) should be presented or not.

		 * @param {mixed} val Optional command value, this can be anything.

		 * @param {Object} a Optional arguments object.

		 * @return {bool} True/false if the command was executed or not.

		 */
  execCommand : function(cmd, ui, val, a) {
   var t = this, s = 0, o, st;
   if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
    t.focus();
   o = {};
   t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
   if (o.terminate)
    return false;
   // Command callback
   if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
    t.onExecCommand.dispatch(t, cmd, ui, val, a);
    return true;
   }
   // Registred commands
   if (o = t.execCommands[cmd]) {
    st = o.func.call(o.scope, ui, val);
    // Fall through on true
    if (st !== true) {
     t.onExecCommand.dispatch(t, cmd, ui, val, a);
     return st;
    }
   }
   // Plugin commands
   each(t.plugins, function(p) {
    if (p.execCommand && p.execCommand(cmd, ui, val)) {
     t.onExecCommand.dispatch(t, cmd, ui, val, a);
     s = 1;
     return false;
    }
   });
   if (s)
    return true;
   // Theme commands
   if (t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
    t.onExecCommand.dispatch(t, cmd, ui, val, a);
    return true;
   }
   // Editor commands
   if (t.editorCommands.execCommand(cmd, ui, val)) {
    t.onExecCommand.dispatch(t, cmd, ui, val, a);
    return true;
   }
   // Browser commands
   t.getDoc().execCommand(cmd, ui, val);
   t.onExecCommand.dispatch(t, cmd, ui, val, a);
  },
  /**

		 * Returns a command specific state, for example if bold is enabled or not.

		 *

		 * @param {string} c Command to query state from.

		 * @return {bool} Command specific state, for example if bold is enabled or not.

		 */
  queryCommandState : function(c) {
   var t = this, o, s;
   // Is hidden then return undefined
   if (t._isHidden())
    return;
   // Registred commands
   if (o = t.queryStateCommands[c]) {
    s = o.func.call(o.scope);
    // Fall though on true
    if (s !== true)
     return s;
   }
   // Registred commands
   o = t.editorCommands.queryCommandState(c);
   if (o !== -1)
    return o;
   // Browser commands
   try {
    return this.getDoc().queryCommandState(c);
   } catch (ex) {
    // Fails sometimes see bug: 1896577
   }
  },
  /**

		 * Returns a command specific value, for example the current font size.

		 *

		 * @param {string} c Command to query value from.

		 * @return {Object} Command specific value, for example the current font size.

		 */
  queryCommandValue : function(c) {
   var t = this, o, s;
   // Is hidden then return undefined
   if (t._isHidden())
    return;
   // Registred commands
   if (o = t.queryValueCommands[c]) {
    s = o.func.call(o.scope);
    // Fall though on true
    if (s !== true)
     return s;
   }
   // Registred commands
   o = t.editorCommands.queryCommandValue(c);
   if (is(o))
    return o;
   // Browser commands
   try {
    return this.getDoc().queryCommandValue(c);
   } catch (ex) {
    // Fails sometimes see bug: 1896577
   }
  },
  /**

		 * Shows the editor and hides any textarea/div that the editor is supposed to replace.

		 */
  show : function() {
   var t = this;
   DOM.show(t.getContainer());
   DOM.hide(t.id);
   t.load();
  },
  /**

		 * Hides the editor and shows any textarea/div that the editor is supposed to replace.

		 */
  hide : function() {
   var t = this, d = t.getDoc();
   // Fixed bug where IE has a blinking cursor left from the editor
   if (isIE && d)
    d.execCommand('SelectAll');
   // We must save before we hide so Safari doesn't crash
   t.save();
   DOM.hide(t.getContainer());
   DOM.setStyle(t.id, 'display', t.orgDisplay);
  },
  /**

		 * Returns true/false if the editor is hidden or not.

		 *

		 * @return {bool} True/false if the editor is hidden or not.

		 */
  isHidden : function() {
   return !DOM.isHidden(this.id);
  },
  /**

		 * Sets the progress state, this will display a throbber/progess for the editor.

		 * This is ideal for asycronous operations like an AJAX save call.

		 *

		 * @param {bool} b Boolean state if the progress should be shown or hidden.

		 * @param {Number} ti Optional time to wait before the progress gets shown.

		 * @param {Object} o Optional object to pass to the progress observers.

		 * @return {bool} Same as the input state.

		 */
  setProgressState : function(b, ti, o) {
   this.onSetProgressState.dispatch(this, b, ti, o);
   return b;
  },
  /**

		 * Resizes the editor to the current contents width and height.

		 */
  resizeToContent : function() {
   var t = this;
   DOM.setStyle(t.id + "_ifr", 'height', t.getBody().scrollHeight);
  },
  /**

		 * Loads contents from the textarea or div element that got converted into an editor instance.

		 * This method will move the contents from that textarea or div into the editor by using setContent

		 * so all events etc that method has will get dispatched as well.

		 *

		 * @param {Object} o Optional content object, this gets passed around through the whole load process.

		 * @return {String} HTML string that got set into the editor.

		 */
  load : function(o) {
   var t = this, e = t.getElement(), h;
   if (e) {
    o = o || {};
    o.load = true;
    h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
    o.element = e;
    if (!o.no_events)
     t.onLoadContent.dispatch(t, o);
    o.element = e = null;
    return h;
   }
  },
  /**

		 * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance.

		 * This method will move the HTML contents from the editor into that textarea or div by getContent

		 * so all events etc that method has will get dispatched as well.

		 *

		 * @param {Object} o Optional content object, this gets passed around through the whole save process.

		 * @return {String} HTML string that got set into the textarea/div.

		 */
  save : function(o) {
   var t = this, e = t.getElement(), h, f;
   if (!e || !t.initialized)
    return;
   o = o || {};
   o.save = true;
   // Add undo level will trigger onchange event
   if (!o.no_events) {
    t.undoManager.typing = 0;
    t.undoManager.add();
   }
   o.element = e;
   h = o.content = t.getContent(o);
   if (!o.no_events)
    t.onSaveContent.dispatch(t, o);
   h = o.content;
   if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
    e.innerHTML = h;
    // Update hidden form element
    if (f = DOM.getParent(t.id, 'form')) {
     each(f.elements, function(e) {
      if (e.name == t.id) {
       e.value = h;
       return false;
      }
     });
    }
   } else
    e.value = h;
   o.element = e = null;
   return h;
  },
  /**

		 * Sets the specified content to the editor instance, this will cleanup the content before it gets set using

		 * the different cleanup rules options.

		 *

		 * @param {String} h Content to set to editor, normally HTML contents but can be other formats as well.

		 * @param {Object} o Optional content object, this gets passed around through the whole set process.

		 * @return {String} HTML string that got set into the editor.

		 */
  setContent : function(h, o) {
   var t = this;
   o = o || {};
   o.format = o.format || 'html';
   o.set = true;
   o.content = h;
   if (!o.no_events)
    t.onBeforeSetContent.dispatch(t, o);
   // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
   // It will also be impossible to place the caret in the editor unless there is a BR element present
   if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) {
    o.content = t.dom.setHTML(t.getBody(), '<br mce_bogus="1" />');
    o.format = 'raw';
   }
   o.content = t.dom.setHTML(t.getBody(), tinymce.trim(o.content));
   if (o.format != 'raw' && t.settings.cleanup) {
    o.getInner = true;
    o.content = t.dom.setHTML(t.getBody(), t.serializer.serialize(t.getBody(), o));
   }
   if (!o.no_events)
    t.onSetContent.dispatch(t, o);
   return o.content;
  },
  /**

		 * Gets the content from the editor instance, this will cleanup the content before it gets returned using

		 * the different cleanup rules options.

		 *

		 * @param {Object} o Optional content object, this gets passed around through the whole get process.

		 * @return {String} Cleaned content string, normally HTML contents.

		 */
  getContent : function(o) {
   var t = this, h;
   o = o || {};
   o.format = o.format || 'html';
   o.get = true;
   if (!o.no_events)
    t.onBeforeGetContent.dispatch(t, o);
   if (o.format != 'raw' && t.settings.cleanup) {
    o.getInner = true;
    h = t.serializer.serialize(t.getBody(), o);
   } else
    h = t.getBody().innerHTML;
   h = h.replace(/^\s*|\s*$/g, '');
   o.content = h;
   if (!o.no_events)
    t.onGetContent.dispatch(t, o);
   return o.content;
  },
  /**

		 * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.

		 *

		 * @return {bool} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.

		 */
  isDirty : function() {
   var t = this;
   return tinymce.trim(t.startContent) != tinymce.trim(t.getContent({format : 'raw', no_events : 1})) && !t.isNotDirty;
  },
  /**

		 * Returns the editors container element. The container element wrappes in

		 * all the elements added to the page for the editor. Such as UI, iframe etc.

		 *

		 * @return {Element} HTML DOM element for the editor container.

		 */
  getContainer : function() {
   var t = this;
   if (!t.container)
    t.container = DOM.get(t.editorContainer || t.id + '_parent');
   return t.container;
  },
  /**

		 * Returns the editors content area container element. The this element is the one who

		 * holds the iframe or the editable element.

		 *

		 * @return {Element} HTML DOM element for the editor area container.

		 */
  getContentAreaContainer : function() {
   return this.contentAreaContainer;
  },
  /**

		 * Returns the target element/textarea that got replaced with a TinyMCE editor instance.

		 *

		 * @return {Element} HTML DOM element for the replaced element.

		 */
  getElement : function() {
   return DOM.get(this.settings.content_element || this.id);
  },
  /**

		 * Returns the iframes window object.

		 *

		 * @return {Window} Iframe DOM window object.

		 */
  getWin : function() {
   var t = this, e;
   if (!t.contentWindow) {
    e = DOM.get(t.id + "_ifr");
    if (e)
     t.contentWindow = e.contentWindow;
   }
   return t.contentWindow;
  },
  /**

		 * Returns the iframes document object.

		 *

		 * @return {Document} Iframe DOM document object.

		 */
  getDoc : function() {
   var t = this, w;
   if (!t.contentDocument) {
    w = t.getWin();
    if (w)
     t.contentDocument = w.document;
   }
   return t.contentDocument;
  },
  /**

		 * Returns the iframes body element.

		 *

		 * @return {Element} Iframe body element.

		 */
  getBody : function() {
   return this.bodyElement || this.getDoc().body;
  },
  /**

		 * URL converter function this gets executed each time a user adds an img, a or

		 * any other element that has a URL in it. This will be called both by the DOM and HTML

		 * manipulation functions.

		 *

		 * @param {string} u URL to convert.

		 * @param {string} n Attribute name src, href etc.

		 * @param {string/HTMLElement} Tag name or HTML DOM element depending on HTML or DOM insert.

		 * @return {string} Converted URL string.

		 */
  convertURL : function(u, n, e) {
   var t = this, s = t.settings;
   // Use callback instead
   if (s.urlconverter_callback)
    return t.execCallback('urlconverter_callback', u, e, true, n);
   // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
   if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
    return u;
   // Convert to relative
   if (s.relative_urls)
    return t.documentBaseURI.toRelative(u);
   // Convert to absolute
   u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
   return u;
  },
  /**

		 * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor.

		 *

		 * @param {Element} e Optional root element to loop though to find tables etc that needs the visual aid.

		 */
  addVisual : function(e) {
   var t = this, s = t.settings;
   e = e || t.getBody();
   if (!is(t.hasVisual))
    t.hasVisual = s.visual;
   each(t.dom.select('table,a', e), function(e) {
    var v;
    switch (e.nodeName) {
     case 'TABLE':
      v = t.dom.getAttrib(e, 'border');
      if (!v || v == '0') {
       if (t.hasVisual)
        t.dom.addClass(e, s.visual_table_class);
       else
        t.dom.removeClass(e, s.visual_table_class);
      }
      return;
     case 'A':
      v = t.dom.getAttrib(e, 'name');
      if (v) {
       if (t.hasVisual)
        t.dom.addClass(e, 'mceItemAnchor');
       else
        t.dom.removeClass(e, 'mceItemAnchor');
      }
      return;
    }
   });
   t.onVisualAid.dispatch(t, e, t.hasVisual);
  },
  /**

		 * Removes the editor from the dom and EditorManager collection.

		 */
  remove : function() {
   var t = this, e = t.getContainer();
   t.removed = 1; // Cancels post remove event execution
   t.hide();
   t.execCallback('remove_instance_callback', t);
   t.onRemove.dispatch(t);
   // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
   t.onExecCommand.listeners = [];
   EditorManager.remove(t);
   DOM.remove(e);
  },
  /**

		 * Destroys the editor instance by removing all events, element references or other resources

		 * that could leak memory. This method will be called automatically when the page is unloaded

		 * but you can also call it directly if you know what you are doing.

		 *

		 * @param {bool} s Optional state if the destroy is an automatic destroy or user called one.

		 */
  destroy : function(s) {
   var t = this;
   // One time is enough
   if (t.destroyed)
    return;
   if (!s) {
    tinymce.removeUnload(t.destroy);
    tinyMCE.onBeforeUnload.remove(t._beforeUnload);
    // Manual destroy
    if (t.theme.destroy)
     t.theme.destroy();
    // Destroy controls, selection and dom
    t.controlManager.destroy();
    t.selection.destroy();
    t.dom.destroy();
    // Remove all events
    // Don't clear the window or document if content editable
    // is enabled since other instances might still be present
    if (!t.settings.content_editable) {
     Event.clear(t.getWin());
     Event.clear(t.getDoc());
    }
    Event.clear(t.getBody());
    Event.clear(t.formElement);
   }
   if (t.formElement) {
    t.formElement.submit = t.formElement._mceOldSubmit;
    t.formElement._mceOldSubmit = null;
   }
   t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
   if (t.selection)
    t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
   t.destroyed = 1;
  },
  // Internal functions
  _addEvents : function() {
   // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
   var t = this, i, s = t.settings, lo = {
    mouseup : 'onMouseUp',
    mousedown : 'onMouseDown',
    click : 'onClick',
    keyup : 'onKeyUp',
    keydown : 'onKeyDown',
    keypress : 'onKeyPress',
    submit : 'onSubmit',
    reset : 'onReset',
    contextmenu : 'onContextMenu',
    dblclick : 'onDblClick',
    paste : 'onPaste' // Doesn't work in all browsers yet
   };
   function eventHandler(e, o) {
    var ty = e.type;
    // Don't fire events when it's removed
    if (t.removed)
     return;
    // Generic event handler
    if (t.onEvent.dispatch(t, e, o) !== false) {
     // Specific event handler
     t[lo[e.fakeType || e.type]].dispatch(t, e, o);
    }
   };
   // Add DOM events
   each(lo, function(v, k) {
    switch (k) {
     case 'contextmenu':
      if (tinymce.isOpera) {
       // Fake contextmenu on Opera
       Event.add(t.getBody(), 'mousedown', function(e) {
        if (e.ctrlKey) {
         e.fakeType = 'contextmenu';
         eventHandler(e);
        }
       });
      } else
       Event.add(t.getBody(), k, eventHandler);
      break;
     case 'paste':
      Event.add(t.getBody(), k, function(e) {
       var tx, h, el, r;
       // Get plain text data
       if (e.clipboardData)
        tx = e.clipboardData.getData('text/plain');
       else if (tinymce.isIE)
        tx = t.getWin().clipboardData.getData('Text');
       // Get HTML data
       /*if (tinymce.isIE) {

								el = DOM.add(DOM.doc.body, 'div', {style : 'visibility:hidden;overflow:hidden;position:absolute;width:1px;height:1px'});

								r = DOM.doc.body.createTextRange();

								r.moveToElementText(el);

								r.execCommand('Paste');

								h = el.innerHTML;

								DOM.remove(el);

							}*/
       eventHandler(e, {text : tx, html : h});
      });
      break;
     case 'submit':
     case 'reset':
      Event.add(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
      break;
     default:
      Event.add(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
    }
   });
   Event.add(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
    t.focus(true);
   });
   // #if contentEditable
   if (s.content_editable && tinymce.isOpera) {
    // Opera doesn't support focus event for contentEditable elements so we need to fake it
    function doFocus(e) {
     t.focus(true);
    };
    Event.add(t.getBody(), 'click', doFocus);
    Event.add(t.getBody(), 'keydown', doFocus);
   }
   // #endif
   // Fixes bug where a specified document_base_uri could result in broken images
   // This will also fix drag drop of images in Gecko
   if (tinymce.isGecko) {
    // Convert all images to absolute URLs
/*				t.onSetContent.add(function(ed, o) {

					each(ed.dom.select('img'), function(e) {

						var v;



						if (v = e.getAttribute('mce_src'))

							e.src = t.documentBaseURI.toAbsolute(v);

					})

				});*/
    Event.add(t.getDoc(), 'DOMNodeInserted', function(e) {
     var v;
     e = e.target;
     if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('mce_src')))
      e.src = t.documentBaseURI.toAbsolute(v);
    });
   }
   // Set various midas options in Gecko
   if (isGecko) {
    function setOpts() {
     var t = this, d = t.getDoc(), s = t.settings;
     if (isGecko && !s.readonly) {
      if (t._isHidden()) {
       try {
        if (!s.content_editable)
         d.designMode = 'On';
       } catch (ex) {
        // Fails if it's hidden
       }
      }
      try {
       // Try new Gecko method
       d.execCommand("styleWithCSS", 0, false);
      } catch (ex) {
       // Use old method
       if (!t._isHidden())
        try {d.execCommand("useCSS", 0, true);} catch (ex) {}
      }
      if (!s.table_inline_editing)
       try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
      if (!s.object_resizing)
       try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
     }
    };
    t.onBeforeExecCommand.add(setOpts);
    t.onMouseDown.add(setOpts);
   }
   // Add node change handlers
   t.onMouseUp.add(t.nodeChanged);
   t.onClick.add(t.nodeChanged);
   t.onKeyUp.add(function(ed, e) {
    var c = e.keyCode;
    if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey)
     t.nodeChanged();
   });
   // Add reset handler
   t.onReset.add(function() {
    t.setContent(t.startContent, {format : 'raw'});
   });
   if (t.getParam('tab_focus')) {
    function tabCancel(ed, e) {
     if (e.keyCode === 9)
      return Event.cancel(e);
    };
    function tabHandler(ed, e) {
     var x, i, f, el, v;
     function find(d) {
      f = DOM.getParent(ed.id, 'form');
      el = f.elements;
      if (f) {
       each(el, function(e, i) {
        if (e.id == ed.id) {
         x = i;
         return false;
        }
       });
       if (d > 0) {
        for (i = x + 1; i < el.length; i++) {
         if (el[i].type != 'hidden')
          return el[i];
        }
       } else {
        for (i = x - 1; i >= 0; i--) {
         if (el[i].type != 'hidden')
          return el[i];
        }
       }
      }
      return null;
     };
     if (e.keyCode === 9) {
      v = explode(ed.getParam('tab_focus'));
      if (v.length == 1) {
       v[1] = v[0];
       v[0] = ':prev';
      }
      // Find element to focus
      if (e.shiftKey) {
       if (v[0] == ':prev')
        el = find(-1);
       else
        el = DOM.get(v[0]);
      } else {
       if (v[1] == ':next')
        el = find(1);
       else
        el = DOM.get(v[1]);
      }
      if (el) {
       if (ed = EditorManager.get(el.id || el.name))
        ed.focus();
       else
        window.setTimeout(function() {window.focus();el.focus();}, 10);
       return Event.cancel(e);
      }
     }
    };
    t.onKeyUp.add(tabCancel);
    if (isGecko) {
     t.onKeyPress.add(tabHandler);
     t.onKeyDown.add(tabCancel);
    } else
     t.onKeyDown.add(tabHandler);
   }
   // Add shortcuts
   if (s.custom_shortcuts) {
    if (s.custom_undo_redo_keyboard_shortcuts) {
     t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
     t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
    }
    // Add default shortcuts for gecko
    if (isGecko) {
     t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
     t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
     t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
    }
    // BlockFormat shortcuts keys
    for (i=1; i<=6; i++)
     t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, '<h' + i + '>']);
    t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
    t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
    t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
    function find(e) {
     var v = null;

     if (!e.altKey && !e.ctrlKey && !e.metaKey)
      return v;

     each(t.shortcuts, function(o) {
      if (tinymce.isMac && o.ctrl != e.metaKey)
       return;
      else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
       return;

      if (o.alt != e.altKey)
       return;

      if (o.shift != e.shiftKey)
       return;

      if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
       v = o;
       return false;
      }
     });

     return v;
    };

    t.onKeyUp.add(function(ed, e) {
     var o = find(e);

     if (o)
      return Event.cancel(e);
    });

    t.onKeyPress.add(function(ed, e) {
     var o = find(e);

     if (o)
      return Event.cancel(e);
    });

    t.onKeyDown.add(function(ed, e) {
     var o = find(e);

     if (o) {
      o.func.call(o.scope);
      return Event.cancel(e);
     }
    });
   }

   if (tinymce.isIE) {
    // Fix so resize will only update the width and height attributes not the styles of an image
    // It will also block mceItemNoResize items
    Event.add(t.getDoc(), 'controlselect', function(e) {
     var re = t.resizeInfo, cb;

     e = e.target;

     // Don't do this action for non image elements
     if (e.nodeName !== 'IMG')
      return;

     if (re)
      Event.remove(re.node, re.ev, re.cb);

     if (!t.dom.hasClass(e, 'mceItemNoResize')) {
      ev = 'resizeend';
      cb = Event.add(e, ev, function(e) {
       var v;

       e = e.target;

       if (v = t.dom.getStyle(e, 'width')) {
        t.dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
        t.dom.setStyle(e, 'width', '');
       }

       if (v = t.dom.getStyle(e, 'height')) {
        t.dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
        t.dom.setStyle(e, 'height', '');
       }
      });
     } else {
      ev = 'resizestart';
      cb = Event.add(e, 'resizestart', Event.cancel, Event);
     }

     re = t.resizeInfo = {
      node : e,
      ev : ev,
      cb : cb
     };
    });

    t.onKeyDown.add(function(ed, e) {
     switch (e.keyCode) {
      case 8:
       // Fix IE control + backspace browser bug
       if (t.selection.getRng().item) {
        t.selection.getRng().item(0).removeNode();
        return Event.cancel(e);
       }
     }
    });
   }

   if (tinymce.isOpera) {
    t.onClick.add(function(ed, e) {
     Event.prevent(e);
    });
   }

   // Add custom undo/redo handlers
   if (s.custom_undo_redo) {
    function addUndo() {
     t.undoManager.typing = 0;
     t.undoManager.add();
    };

    // Add undo level on editor blur
    if (tinymce.isIE) {
     Event.add(t.getWin(), 'blur', function(e) {
      var n;

      // Check added for fullscreen bug
      if (t.selection) {
       n = t.selection.getNode();

       // Add undo level is selection was lost to another document
       if (!t.removed && n.ownerDocument && n.ownerDocument != t.getDoc())
        addUndo();
      }
     });
    } else {
     Event.add(t.getDoc(), 'blur', function() {
      if (t.selection && !t.removed)
       addUndo();
     });
    }

    t.onMouseDown.add(addUndo);

    t.onKeyUp.add(function(ed, e) {
     if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey) {
      t.undoManager.typing = 0;
      t.undoManager.add();
     }
    });

    t.onKeyDown.add(function(ed, e) {
     // Is caracter positon keys
     if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) {
      if (t.undoManager.typing) {
       t.undoManager.add();
       t.undoManager.typing = 0;
      }

      return;
     }

     if (!t.undoManager.typing) {
      t.undoManager.add();
      t.undoManager.typing = 1;
     }
    });
   }
  },

  _convertInlineElements : function() {
   var t = this, s = t.settings, dom = t.dom, v, e, na, st, sp;

   function convert(ed, o) {
    if (!s.inline_styles)
     return;

    if (o.get) {
     each(t.dom.select('table,u,strike', o.node), function(n) {
      switch (n.nodeName) {
       case 'TABLE':
        if (v = dom.getAttrib(n, 'height')) {
         dom.setStyle(n, 'height', v);
         dom.setAttrib(n, 'height', '');
        }
        break;

       case 'U':
       case 'STRIKE':
        //sp = dom.create('span', {style : dom.getAttrib(n, 'style')});
        n.style.textDecoration = n.nodeName == 'U' ? 'underline' : 'line-through';
        dom.setAttrib(n, 'mce_style', '');
        dom.setAttrib(n, 'mce_name', 'span');
        break;
      }
     });
    } else if (o.set) {
     each(t.dom.select('table,span', o.node).reverse(), function(n) {
      if (n.nodeName == 'TABLE') {
       if (v = dom.getStyle(n, 'height'))
        dom.setAttrib(n, 'height', v.replace(/[^0-9%]+/g, ''));
      } else {
       // Convert spans to elements
       if (n.style.textDecoration == 'underline')
        na = 'u';
       else if (n.style.textDecoration == 'line-through')
        na = 'strike';
       else
        na = '';

       if (na) {
        n.style.textDecoration = '';
        dom.setAttrib(n, 'mce_style', '');

        e = dom.create(na, {
         style : dom.getAttrib(n, 'style')
        });

        dom.replace(e, n, 1);
       }
      }
     });
    }
   };

   t.onPreProcess.add(convert);

   if (!s.cleanup_on_startup) {
    t.onSetContent.add(function(ed, o) {
     if (o.initial)
      convert(t, {node : t.getBody(), set : 1});
    });
   }
  },

  _convertFonts : function() {
   var t = this, s = t.settings, dom = t.dom, fz, fzn, sl, cl;

   // No need
   if (!s.inline_styles)
    return;

   // Font pt values and font size names
   fz = [8, 10, 12, 14, 18, 24, 36];
   fzn = ['xx-small', 'x-small','small','medium','large','x-large', 'xx-large'];

   if (sl = s.font_size_style_values)
    sl = explode(sl);

   if (cl = s.font_size_classes)
    cl = explode(cl);

   function process(no) {
    var n, sp, nl, x;

    // Keep unit tests happy
    if (!s.inline_styles)
     return;

    nl = t.dom.select('font', no);
    for (x = nl.length - 1; x >= 0; x--) {
     n = nl[x];

     sp = dom.create('span', {
      style : dom.getAttrib(n, 'style'),
      'class' : dom.getAttrib(n, 'class')
     });

     dom.setStyles(sp, {
      fontFamily : dom.getAttrib(n, 'face'),
      color : dom.getAttrib(n, 'color'),
      backgroundColor : n.style.backgroundColor
     });

     if (n.size) {
      if (sl)
       dom.setStyle(sp, 'fontSize', sl[parseInt(n.size) - 1]);
      else
       dom.setAttrib(sp, 'class', cl[parseInt(n.size) - 1]);
     }

     dom.setAttrib(sp, 'mce_style', '');
     dom.replace(sp, n, 1);
    }
   };

   // Run on cleanup
   t.onPreProcess.add(function(ed, o) {
    if (o.get)
     process(o.node);
   });

   t.onSetContent.add(function(ed, o) {
    if (o.initial)
     process(o.node);
   });
  },

  _isHidden : function() {
   var s;

   if (!isGecko)
    return 0;

   // Weird, wheres that cursor selection?
   s = this.selection.getSel();
   return (!s || !s.rangeCount || s.rangeCount == 0);
  },

  // Fix for bug #1867292
  _fixNesting : function(s) {
   var d = [], i;

   s = s.replace(/<(\/)?([^\s>]+)[^>]*?>/g, function(a, b, c) {
    var e;

    // Handle end element
    if (b === '/') {
     if (!d.length)
      return '';

     if (c !== d[d.length - 1].tag) {
      for (i=d.length - 1; i>=0; i--) {
       if (d[i].tag === c) {
        d[i].close = 1;
        break;
       }
      }

      return '';
     } else {
      d.pop();

      if (d.length && d[d.length - 1].close) {
       a = a + '</' + d[d.length - 1].tag + '>';
       d.pop();
      }
     }
    } else {
     // Ignore these
     if (/^(br|hr|input|meta|img|link|param)$/i.test(c))
      return a;

     // Ignore closed ones
     if (/\/>$/.test(a))
      return a;

     d.push({tag : c}); // Push start element
    }

    return a;
   });

   // End all open tags
   for (i=d.length - 1; i>=0; i--)
    s += '</' + d[i].tag + '>';

   return s;
  }

  /**#@-*/
 });
})();
