// ==UserScript==
// @name        OSpell - A Better Opera Spell Checker
// @author      Sombria (additional coding by diz)
// @version     1.06
// @namespace   http://my.opera.com/Sombria/blog/userjs-orangoo-spell-check
// @modified    2008-12-08
// ==/UserScript==

(function() {

/* We recommended using external preferences file
instead of changing settings in this script. */
var pref = {
  ENGINE: 2,
  //ENABLED_LANGS: ['en','pl'],
  defaultLang: '',
  ENABLED_ELEMS: { textarea: true, input: false },
  navbarType: 1, // 0-disabled, 1-normal, 2-without background
  longLangNames: true,
  alignNavbar: 2, // 0-left, 1-middle, 2-right
  confirmCancel: false,
  enableShortcuts: true,
  shortcutCheck: {keyCode: 44, ctrlKey: true, altKey: false, shiftKey: false},
  shortcutOK: {keyCode: 13, ctrlKey: true, altKey: false, shiftKey: false},
  shortcutCancel: {keyCode: 39, ctrlKey: true, altKey: false, shiftKey: false},
  shortcutChangeLang: {keyCode: 76, ctrlKey: true, altKey: false, shiftKey: false},
  zIndex: 100001,

  engines: [
    {
      // HTTP Gmail
      href: "http://mail.google.com/mail/?ui=1&view=sc&scl=",
      frameSrc: "http://mail.google.com/mail/html/load.html?ospellUserjs",
      // HTTPS Gmail (to use in https gmail, uncomment 2 below lines and comment ones above)
      //href: "https://mail.google.com/mail/?ui=1&view=sc&scl=",
      //frameSrc: "https://mail.google.com/mail/html/load.html?ospellUserjs",
      gmailType: true,
      languages: ['bg','ca','cs','da','de','el','en','en-GB','es','et','fi','fr','hi','hr',
        'hu','id','is','it','lt','nl','no','pl','pt-BR','pt-PT','ru','sk','sl','sv','uk','vi']
    },
    {
      href: "http://orangoo.com/noxspell/?lang=",
      frameSrc: "http://orangoo.com/noxspell/?ospellUserjs",
      languages: ['ca','cs','da','de','el','en','es','et','fi','fr','he','hi','hr',
        'id','it','lt','lv','nl','no','pl','pt','ro','ru','sk','sl','sv','tr']
    },
    {
      href: "http://fearphage.com/spell/sendReq.php?lang=",
      frameSrc: "http://fearphage.com/?ospellUserjs",
      languages: ['da','de','en','es','fr','it','nl','pl','pt','ru','fi','sv']
    }
  ],

  setPreferences: function(options) {
    for (key in options)
      this[key] = options[key];
  }
};
if (typeof(OSExtPref) != 'undefined')
  pref.setPreferences(OSExtPref);

var OS = {

  engine: pref.engines[pref.ENGINE],
  langDefs: {
    'bg': '\u0411\u044A\u043B\u0433\u0430\u0440\u0441\u043A\u0438',
    'ca': 'Catal\xE0',
    'cs': '\u010Ce\u0161tina',
    'da': 'Dansk',
    'de': 'Deutsch',
    'el': '\u03B5\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC',
    'en': 'English',
    'en-GB': 'english (GB)',
    'es': 'Espa\xF1ol',
    'et': 'Eesti keel',
    'fi': 'Suomi',
    'fr': 'Fran\xE7ais',
    'he': '\u05E2\u05B4\u05D1\u05B0\u05E8\u05B4\u05D9\u05EA',
    'hi': '\u0939\u093F\u0928\u094D\u0926\u0940',
    'hr': 'Hrvatski',
    'hu': 'Magyar',
    'id': 'Bahasa\u00A0Indonesia',
    'is': '\xCDslenska',
    'it': 'Italiano',
    'lt': 'Lietuvi\u0173',
    'lv': 'Latvie\u0161u',
    'nl': 'Nederlands',
    'no': 'Norsk',
    'pl': 'Polski',
    'pt': 'Portugu\xEAs',
    'pt-BR': 'Portugu\xEAs',
    'pt-PT': 'Portugu\xEAs (Portugal)',
    'ro': 'Rom\xE2n\u0103',
    'ru': '\u0420\u0443\u0441\u0441\u043A\u0438\u0439',
    'sk': 'Sloven\u010Dina',
    'sl': 'Sloven\u0161\u010Dina',
    'sv': 'Svenska',
    'tr': 'T\xFCrk\xE7e',
    'uk': '\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430',
    'vi': 'Ti\u1EBFng Vi\u1EC7t'
  },


/********************************
* Main initialization functions *
********************************/

  init: function() {
    this.setEnabledLanguages();
    if ( location.href.indexOf(this.engine.frameSrc)==0 && self!=top )
      this.initEngineServer();
    else
      this.initPage();
  },

  initEngineServer: function() {
    // -------------------------  frame with sc engine ---------------------------
    var me = this;
    // disabled window.stop() for now (broken 9.5 builds)
    //window.stop();

    this.cookieName = 'OS_lang';

    this.request = new XMLHttpRequest();
    this.request.onreadystatechange = function() {
      if (this.readyState != 4) return;
      if (this.status != 200) {
        me.sendMsg(me.messageSource, 'ERROR', this.statusText);
        return;
      }
      me.sendMsg(me.messageSource, me.currentLang, this.responseText);
    };

    window.addEventListener('message', function(e) {
      me.messageSource = e.source;
      var msg = me.parseMsg(e.data);
      if (!msg) return;
      switch (msg.type) {
        case 'STOP' :
          me.request.abort();
          return;
        case 'LANG' :
          me.setLang(msg.value);
          me.sendMsg(me.messageSource, 'LANG', me.currentLang);
          return;
        default :
          me.sendRequest(msg.type, msg.value);
          break;
      }
    }, false);
  },

  initPage: function() {
    // ------------------------------ other sites --------------------------------
    var me = this;
    this.images = {
      SpellBack: 'data:image/gif;base64,R0lGODlhCgAtAMYBAP/+/////////v/+/v7///7+//7//v7+/v79/v3+/v39/f79/f7+/f39/v3+/f38/fz9/Pz8/f38/P39/Pz9/fz8/Pv8/Pz7/Pv7+/v7/Pv8+/z8+/z7+/v6+/r7+/v7+vv6+vr6+/r7+vr6+vn6+fn6+vn5+fr6+fr5+fr5+vj4+Pf4+Pj3+Pj49/j39/f39/f49/b29vX19fX29vb29fb19vX29fb19fT09PTz9PP09PPz8/P08/Ly8vPz8vHy8fLy8fLx8vHx8fHy8vDw8PDv7/Dw7/Dv8O/w7+/w8O/v7+7u7u3u7e7t7u3t7e7t7e3u7u7u7ezs7Ovr6+zs6+vs6+zr7Ovs7Orq6uvr6unq6erp6unp6enp6urp6enq6urq6eno6Ojp6ejo6Ojo6ejp6Ono6enp6Ofo6Ofn6Ojo5+fn5+jn5+jn6Ofo5+bm5+fm5ubn5ubn5+fm5+fn5ubm5v///////////////////////////////////////ywAAAAACgAtAAAH/oAAAAEAAoMDAoYGBwUHjgcGAY+TCI4LCAwLDQsKDA0JCqGiow8KEAoPERKiFRYVr7CwFxgXGRYYGBkaGB0eHr0euB8iISEjxcchICMkzCQlIyMm09TV0yomKtrb2CorKiwt4i3eLy8x5ubo5zHt7uwyMzQ0NTYyMTUzODL8+/0yOAIKHLgjx44dPBAaPLijRw8fDn007PGjBxAgPy72CCKko8ePREJ2DEmySBEjRI6YRJIkiZKXMJcseSlTppKaN5k0cbJkp5MmT5g4GepEClEpSJMqVTqFShUpVqxMkTIFS9UsU6ZksYqlq1csW7Fo2bKFSxcuXLCQRcu2LZcwPGLGiIErl0yYMXjz6k2jJs0aNn/ToGmzprDhwm7WvIETR47hOXTW0KkT500cOnLq1JmsubPnz6BDi/YcCAA7',
      Background: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAZCAQAAACbHsZYAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAmJLR0QA/4ePzL8AAABzSURBVDjLvZKxDYAwDATvwQXTsSNr0VGyAxCaCJCwHBeIa3Py+xVrLlTEm+uRjiTfi3ZvpqzoDT880UOe2JgYt+myIv+I8V8/xHTreAnrw2h54t9ltBVSiIkehcdYKOxixJp3frAZC5niu7E2gmu4GHLiCem0GAkqliAIAAAAAElFTkSuQmCC',
      Left: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAZCAQAAAC4RZeNAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAmJLR0QA/4ePzL8AAABzSURBVBiVY2BgYRBgKLr9/yYQMjEgAdwcFgjFCIRwDhNOZYRkCHKYMe2Bc/4ToewXSA6i+B/DnYtfmRlAkAko++Uki76+DCsjkMPGwMMgymDKoMMM9dl/hncM95mhHv3L8IfhJ4QDMuQPwy9GeBAwMTABAAAnGaCX4eklAAAAAElFTkSuQmCC',
      Right: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAZCAQAAACmjKc+AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAmJLR0QA/4ePzL8AAACPSURBVBiVjc5NDgFBFEXhrylLtgYTOzA0twR7MJAwEpFgpiPSpLtUGfhLELw7Ozn35oV5hv560DWzVQYv9wcoQPEErc+VN6N4NXwxfoP2T+Pt0/C1kuX76FmUHyCZLFUaidCx0VuMRw5qUQqdoZ2plb1KlIKRWmWvdNRIwVKjVjk6iXKwkURR7XwFJdItuACbtz7fiJ9mpwAAAABJRU5ErkJggg==',
      Cancel: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAADtElEQVQ4jX3U609bdRgH8J+XaDQaL/MSo0aj8fVe+YaBmVTo5bSFheiM2YzzT9gb1M1mMMheOC7ZJZlzMjFxcinTmGg0y1BgoIPSi/TCLdhy2h56v8E5rC39+pxDa1pseV416e98znP7HcbK4scjR0wz3d3zP7S2XgBj97F9YpTjPp8wmSZHDYbPqh4w63TnIm63tLOzg4wg5AkfrIWa9foeYWFBpABvsYgjWm1XxYExjuuOuFxSIZXATs+nyGfSSAUC+bGWlut7UTPH9crYZtAP/uwnyEQjWJ+bk4Y0mnO7GKVMmChjhbY3kX2DQTragGw6hSTP581G49cllMrsE5xOScaWDh+E9RkGd6sKqXAIPkJHOK6DTXd2ThYKBRQunlEw8XWGzGsM6XcbcC+VRMLny5kNhmvUkn6BqijH5p5mmHmKwXv1EtLpNCY6OqxsTK8/HV1cFPOSiO0TGgVLvMoQeYUh2tYAKRFHeHk5F7Tbt0uYrYj9SZjr4/eRSiSwsbSUGzYY+nYnZjCcjxCaE7eQ+UijYMLLDPxLDIHWeojxGBTs7SJ2oBILejw5KvdaRb9v0uTCHo+Y3dpE9JhawbwvMqy+wPCPof4/zELYX+WY250d1um+qroRctNDtDoSTVn4QK1gi88zOJ9jsFfB/C5XljL7ct+dldENt3tbpCl732v6P3bi6C7mdMqZXakJySG/SZ5obG0tK/dshcq0P8swT9hdGsL0k/S7+RCStCa8w5Gl3buwL0Y7OUBYroQ59mATTzDcfpwGojqEuCBg3eG4N6RW99fCrse83nw5Zi1irg/bYDWqFOy3xxh+fpThj8N1iAWD8Nls2xWoghmNgzGfT8FWGw/ibxmjvs0WMblnyVAId/Uq/ELYT48w3HyY4dZbdYgGAvBaLBKhuztIX5bL8fX1/JYQqInJ14pfWMjGNzYwpVUp2MhDDDcepGzr6xDieazNzoo0pNPsTleXTZIkCOc7FKx0C0oYb7fLb+8f1euvygsco97dbm5UsG8fYPjmfkqgrxeRSAS32tunmHyh/fPz4iY9vHK8RcHcRcxvt4vDGk1vqTXybQh4PPko9e7XdxoVbPz4MUTCYSxPTma+V6vblbLpobN+i2UzE4+DH7iCdDIJ3mYTR3S6nr3Do7IG/ISG/X7YL19SsJWpqa3vmpo6KyZN6BnvzEyGArzVKg5rtV/UWq8hrXaQJ1Quc3V6WrrR3NxV7SyjjE7+furUOGHVP+vlKMddHDeZbISbyv/7F2lPHK8b0cZDAAAAAElFTkSuQmCC',
      Ok: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAC4klEQVQ4y2NgIBPYlPibJ/YWLTXK8XRioBg4MLCUz2k9/h8IDl468SGqI3u5SJCmJNnmeVTF1L/5+O7foXtH/ovWy/xfcX71/1nblt73r0ssJNkwsUgd8RlbFt/9/ff3f51u4/8MxRz/eapE/v/99/d/16pp10g2MKote+mfv3/+Tzw8BWwYCDftavv/6/fv/yHN6YtJMswkx8PhwMVj7199fv1foEYcbJhKu/b/77+//5+6aeFtXl81EVLMY8ybVncAFBFJK9Phrttybdv/Vx/e/HWriKwmyXWOJaFFj149/X38wcn/jCWcYMO85wSCzP9fMqv5KEMoAzPxpnkK8XWsnHoDFPAmE6zAhrGX8/+/9fr2/5M3zn8yy/X2xtBjlOWiaZbt5YPNPN/a+Jnff/74P/vEPLhXa7Y3/P/379//2K78NZg6gM7Nn1Z35Myti1/T+ks3aabZGsGk1FNs9dYe2frizde3/0XqpMGGyTWr/v/66+v/hbtXP1SMspLH9FFNbMuzdy/+LT234v/7bx/+bz2552V4S8Yi8UA9sZS+0q2gcMpcmwd33aoLa/9//Prpv09tfCeGYWoJDhrL9q17UrejCaxYokH+/9yTC/7//P3z/+QN8x7fenLv2/mnF/8zl3KD5Z2me4Ajom5hzxkGTxV2DAPTJ5Zv+vnn53+uCiG4C0DYfKLtf1CMgsLJdoozWIy1jPf/9Zc3/l97eOu7bVFADNYIDGhIKpi4fs6dw/eO/beYZIdiKCh5OExzg/OLN5WDXQcKBrypgt9bVzCsNWPumsNbXy07txIc6MgGw4Li4/eP/9cd2f5cM9lBh6jkppfhppPcV7Ll6LVTX1t2d4AzPczAxWeW/QclnaDGlGmkF56FQYGls5uPX3h4+W/qqqz/EYtjwWHZuWrqFQYHUR7yCr0GBibXysiy7tXTb3z78f3/w5dPfjuUBmdToXQW5QlpSusPbEjuBhUQhJQDACIxsgp85xwAAAAAAElFTkSuQmCC',
      Start: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAACa0lEQVQ4y+2TTUiUQRjH/zPvq+hu65quH6SHVIQ0W3WxFKJL5SHLoCCCIKggog5CXatrUCeJLD1YHgLxIIpLBkKQRUR16LvExT5Q0dx93dVdd9+vmafD7oZZGxGdogce5j/PML/5zzMM8M/G1g4FbT3ZfG2d/ylQ2iitKd8y1H7d5fgrQBLgHlfpfm9Vg7+9y5WXrrNMGw73FRW68lzVAIGIQABABErNo5rhaard5le5ikhcezLx+d2+wZNaKCOwo7/5eF1l400JAUkCkiQopQkyWYMAUVIvJbRXE5/e7lHXglo7s/liQEiiH90TCLodQ9RaQNyOwJQx2JSAZAbAhDcrV3Z9B9zb6XY0bPL1j755cGolFte1pWBYQkKSibA5jUV9FqaIgXGAMbBsB/IVBWAciGn08OWgOP3NRVun29Wyefuw25m/81bvQNmLXhEEkJOpJRWtvGTXuayAmgWEZ2noUbd9bOaxXFYB4OCNokJvVeMdt9PdbMo4yhscvsrbRogxpN2A8eQTcg4YKwhM3RNxzkELU9R997x1NhEiAwDUQz2lG2oqakedDke9LmOI6LMo2Uh+yRh4CsRTmdZhCyf0MPwzr+nC2EXzip2AnXauelzF13Jzc+ot0jEXfY9laz65GSlnqcSqEQCbHBGhyRFxaW0r1PsDk2d2H1E9Yt3CjpgVhKIAloEIYyDOV0FXXd1YIT1TbxkAlLXwPN9Rta+gjB3gKjB+1ar+MCa//OKj6ACsny0oABCdIWP6qRwu8fIC53rWFJ6jy/PPSQNgZkiZ6SQlLew4xMdxMVZcp+jBAD3TJiiO//E78RURYhs0oomwpwAAAABJRU5ErkJggg==',
      Stop: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAADv0lEQVQ4y4WUS2hcZRiGn3OdOTOZybSRxDRxNBE1i9JA2lpJFFLFgBGRIFXqRhEXAZG6UVCpTVVQXKhYxboQpXQhaETrovSaRW+W1kCUJE47ZpJObk3S6VySmTNz/nN+F82EaZLqCy//4Yf34fu+8/Er3EE/NjZuuL+9vdvy+XYoQtRL18WTcqpQKFwcu3Tp6PPXr6fXyylrQA0NkYc6OvYFTPNVzbarpG0jhQAhkEKgAELXswXP+2Z0cPDD3el09o7AgY6OtoZotN8Q4j7pOCsgKr5XTtfF8fn+mc7lep4cGflrDfD49u2tTU1NA5rnbUAIvGKRmYWF0dlM5uiNXC6G57HR72+pN82na/3+BxXXRQqB0PWFCdvu7B4bG14BHggEqp7o6hr0a9oDCMFiJpM5G4+/2Tc5eXgOCpVdbILA3kjk5UcikY8DihKSQmAbxsjx8fFtb3leQQH4vbNzf21NzXsIwVI2m+sfGuruS6XOAhxpbGwxICIdB8dxUs+mUlcAPrCsnc9EIr/5IQgwr+vvPJZMfqS/bZqBkGX14jh4jsNQIvFuGbYFtHui0dMhy6rHcUin0+OkUs2A3FsoDDSr6v6todAnAFWu+9oeRflUfTgafdQyjFopBOlUKvn1xMS3lQOWrqvhukjHQdi2Vtn+90tLB2/m87OiWMQslRraQ6Ed+sZweKt0HBCC3NLSyXNS5itD+clJSrkczuIii55324qdgNwb2eyAAbsVIGia23RVyjqEuFVBsRhfvZf5qSmCgLrs1fIgXi5bE6JOdx3HLVeoe56xOqAvm4qzUgYYank8Urp6Pp9PlhfXtO3NqwNaBUhbBxiAzeV7W1WvqbH5+QtusSidRAJzaqrrc6hZr0JjHeBXcHcIdqoAqipjcEE9mMsNzsdif3tzcwQg3Ar7yoGbIPN+/6wdCGQKwWCmYFmzlcAt8H4AghqQ0bQ/D7juMACHfL5XRlRVjoIcBu8k9N7eFeFlBwB2AadhTwy8OMirquodsqwXVxLVYJy2rGNXQF4FGQPvInzXD/f2VZBfB36G5stwOA4yATKhKHIgGDyiLY965XF4Sdfren2+Y3X5fCtSAiDAseEPCTFAUaHFgjajHFYUZqqqLn9WKDz1gxALa/7YC7peeyoc/nXcNL1rIMtOVnhy2UnT9E5VV//Uo+t38T/Sv4xEdp0Lh88ngsHStGHIaUWR04oiZ0xTjgeDpfPV1We+iER61ltN5b/Az+l60+PhcFuNaW4CuFEqTZ3IZAZ/cd3xWxNZq38BrObVY9DjfwQAAAAASUVORK5CYII=',
      Loading: 'data:image/gif;base64,R0lGODlhKwALAPEAAP///58REc+Kip8RESH/C05FVFNDQVBFMi4wAwEAAAAh/h1CdWlsdCB3aXRoIEdJRiBNb3ZpZSBHZWFyIDQuMAAh/hVNYWRlIGJ5IEFqYXhMb2FkLmluZm8AIfkECQoAAAAsAAAAACsACwAAAjKEjgjLltnYg/PFChveVvPLheA2hlhZoWYnfd6avqcMZy1J14fKLvrEs/k+uKAgMkwVAAAh+QQJCgAAACwAAAAAKwALAAACPcSOCMsgD2FjsZqEx6x885hh3veMZJiYn8qhSkNKcCy4B2vNsa3pJA6yAWUUGm9Y8n2Oyk7T4posYlLHrwAAIfkECQoAAAAsAAAAACsACwAAAj2EjgjLMA9hY6maalvcb+IPChO3eeF5jKTUoKi6DqYLwutMYzaJ58nO6flSmpisNcwwjEfK6fKZLGJSqK4AACH5BAkKAAAALAAAAAArAAsAAAJAhI4Iy5bZ2JiUugcbfrH6uWVMqDSfRx5RKnQnxa6p+w6xNpu1nY/9suORZENd7eYrSnbIRRMQvGAizhAV+gIUAAA7AAAAAAAAAAAA'
    };

    this.navBar = null;
    this.spellBox = null;
    this.suggestBox = null;
    this.langBox = null;
    this.state = 0; // 0 - disabled, 1 - navBar enabled, 2 - spellBox enabled, 3 - loading, 4 - info (error, warning etc.)
    this.currentElement = null; // focused textarea
    this.messageSource = null;
    this.engineFrame = null;
    this.selection = {'prefix': '', 'text': '', 'postfix': ''};

    window.addEventListener('message', function(e) {
      if (me.messageSource != e.source) return;
      var msg = me.parseMsg(e.data);
      if (!msg) return;

      switch (msg.type) {
        case 'LANG' :
          me.currentLang = msg.value;
          if (me.navBar) {
            me.navBar.selectLang.showLang(msg.value);
            if (me.state) me.showNavBar();
          }
          return;
        case 'ERROR' :
          me.showInfo('ERROR ' + msg.value + '\nFor HTTPS Gmail, modify ospell.js', true);
          return;
        default :
          if (me.state == 3) {
            var res = me.parseResult(msg.value);
            if (typeof(res) == 'string')
              me.showInfo(res, true);
            else {
              if (!res.length)
                me.showInfo('No spelling errors found');
              else {
                me.displayResult(res);
                me.currentElement.blur();
                me.setDisplayState(2);
              }
            }
          }
          break;
      }
    }, false);

    opera.addEventListener('BeforeEvent.focus', function(e) {
      var target = e.event.target;
      if ( typeof(target.nodeName) != 'string' || me.state > 1 || !pref.ENABLED_ELEMS[target.nodeName.toLowerCase()] ) return;
      if ( target.nodeName.toLowerCase()=='input' && target.type!='text' ) return;
      me.currentElement = target;
      me.currentElement.pos = me.absolutePos(me.currentElement);
      me.setDisplayState(1);
      me.currentElement.addEventListener('DOMAttrModified', OS.placeSCElements, false);
    }, false);

    opera.addEventListener('BeforeEvent.blur', function(e) {
      if (me.state > 1) return;
      if (e.event.target == me.currentElement) {
        me.setDisplayState(0);
        me.currentElement.removeEventListener('DOMAttrModified', OS.placeSCElements, false);
      }
    }, false);

    opera.addEventListener('BeforeEvent.submit', function() {
      if (me.state < 2 || me.state == 4 ) return;	// fixed submitting 'No spelling errors found' text
      me.replaceSelection(me.spellBox.textContent);
    }, false);

    opera.addEventListener('AfterEvent.load', function(e){
      if ( !(e.event.target instanceof HTMLDocument) ) return;
      me.placeSCElements();
      document.addEventListener('resize', function(){ me.placeSCElements(); }, false);
    }, false);
  },


/*******************
* Helper functions *
*******************/

  absolutePos: function(_node) {
    var pos = {'left': _node.offsetLeft, 'top': _node.offsetTop,
      'width': _node.offsetWidth, 'height': _node.offsetHeight};
    while (_node.offsetParent) {
      _node = _node.offsetParent;
      pos.left += _node.offsetLeft;
      pos.top += _node.offsetTop;
    }
    if (!this.rootComp)
      this.rootComp = {
        left: document.documentElement.offsetLeft+document.body.clientLeft,
        top: document.documentElement.offsetTop+document.body.clientTop
      };

    pos.left -= this.rootComp.left;
    pos.top -= this.rootComp.top;
    return pos;
  },

  showInViewport: function(_el, _x, _y) {
    _el.style.visibility = 'hidden';
    _el.style.left = 0;
    _el.style.top = 0;
    _el.style.display = 'block';
    var max_x = window.innerWidth + pageXOffset - this.rootComp.left - _el.offsetWidth;
    var max_y = window.innerHeight + pageYOffset - this.rootComp.top - _el.offsetHeight;
    if (_x > max_x) _x = max_x;
    if (_y > max_y) _y = max_y;
    if (_x < pageXOffset) _x = pageXOffset;
    if (_y < pageYOffset) _y = pageYOffset;
    _el.style.left = _x;
    _el.style.top = _y;
    _el.style.visibility = 'visible';
  },

  equalKeys: function(_a, _b) {
    return (_a.keyCode == _b.keyCode && _a.shiftKey == _b.shiftKey && _a.ctrlKey == _b.ctrlKey
      && _a.altKey == _b.altKey);
  },

  htmlSpecial: function(_text) {
    return _text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
  },

  isDescendant: function(_descendant, _node) {
    if( !_descendant || !_node ) return false;
    do {
      if (_node == _descendant) return true;
      _descendant = _descendant.parentNode;
    } while (_descendant);
    return false;
  },

  isIn: function(elm, list) {
    // returns 1 based index
    for (var i=0; i < list.length; i++) {
      if (elm == list[i])
        return i+1;
    }
    return 0;
  },


/***************************
* Cross document messaging *
***************************/

  sendMsg: function(_src, _type, _value) {
    if (_src)
      _src.postMessage('OS*' + _type + '*' + _value);
  },

  parseMsg: function(_msg) {
    var matches = _msg.match(/^OS\*([^\*]*)\*([\s\S]*)/);
    if (!matches) return null;
    return {type: matches[1], value: matches[2]};
  },

  createRequestData: function(_msg) {
    if (this.engine.gmailType) return _msg.replace(/&/g, ' ');
    return '<?xml version="1.0" encoding="utf-8" ?><spellrequest textalreadyclipped="0" ignoredups="0" ignoredigits="1" ignoreallcaps="1"><text>' + this.htmlSpecial(_msg) + '</text></spellrequest>';
  },

  sendRequest: function(_lang, _msg) {
    this.setLang(_lang);
    this.request.abort();
    this.request.open('POST', this.engine.href+this.currentLang, true);
    this.request.send(this.createRequestData(_msg));
  },

  createEngineFrame: function() {
    var me = this;
    var tmp = document.createElement('iframe');
    tmp.style = 'width:0; height:0; margin:0; position:absolute; visibility:hidden;';
    tmp.src = this.engine.frameSrc;
    tmp.addEventListener ('load', function(e) {
      this.removeEventListener('load',arguments.callee,false);
      me.messageSource = ( window.postMessage ? this.contentWindow : this.contentDocument );
      me.sendMsg(me.messageSource, 'LANG', '');
      if (me.X) me.startSpellCheck();
    }, false);
    this.engineFrame = tmp;
    this.currentLang = '';
    document.body.appendChild(tmp);
  },


/************************
* Interface controlling *
************************/

  createNavBar: function() {
    var me = this;
      // skin:
    this.navBar = document.createElement('customDiv');
    this.navBar.style = 'display:none; z-index:' + pref.zIndex + '; height:25px; color:#000 !important; padding:0; margin:0; text-align: center; vertical-align: top; position:absolute; font: bold 11px Tahoma, Arial, sans-serif !important; background: transparent; opacity: .6';
    this.navBar.onmouseover = function(e){ this.style.opacity='1'; };
    this.navBar.onmouseout = function(e){ this.style.opacity='.6'; };
    if (pref.navbarType == 1) {
      this.navBar.style.background = 'url(' + this.images.Background + ') repeat-x'
      var tmp = document.createElement('customDiv');
      tmp.style = 'position:absolute; display:block; height:25px; width:6px; top:0; left:-6px; padding:0; margin:0; background: url(' + this.images.Left + ') no-repeat scroll 0 0;';
      this.navBar.appendChild(tmp);
      tmp = document.createElement('customDiv');
      tmp.style = 'position:absolute; display:block; height:25px; width:8px; top:0; right:-8px; padding:0; margin:0; background: url(' + this.images.Right + ') no-repeat scroll 0 0;';
      this.navBar.appendChild(tmp);
    }

      // select languages
    tmp = document.createElement('customSpan');
    tmp.style = 'display:inline; padding:1px; background:transparent; border:none; cursor:pointer; margin:0 4px 0 0; width:auto;';
    tmp.textContent = 'Loading...';
    tmp.onmouseover = function(){
      this.style.textDecoration='underline';
      if (pref.navbarType == 2) this.style.background='#fff';
    }
    tmp.onmouseout = function(){
      this.style.textDecoration='none';
      this.style.background='transparent';
    }
    tmp.showLang = function(_lang) {
      this.textContent = pref.longLangNames ? me.htmlSpecial(me.langDefs[_lang]) : _lang.toUpperCase();
    }
    tmp.addEventListener('click', function(e) {
      //e.stopPropagation();
      if (me.state <= 1) me.setDisplayState(1);
      me.showLangBox();
    }, false);
    if (this.engine.languages.length > 1) this.navBar.appendChild(tmp);
    this.navBar.selectLang = tmp;


      // buttons Start, Stop, Ok, Cancel
    var buttons = ['Cancel', 'Ok', 'Start', 'Stop'];
    for (var i=0; i<4; i++) {
      tmp = document.createElement('img');
      tmp.src = this.images[buttons[i]];
      tmp.style = 'display:none; margin:0; padding:0; vertical-align:middle; cursor:pointer;';
      tmp.title = buttons[i];
      this.navBar.appendChild(tmp);
      this.navBar['button' + buttons[i]] = tmp;
    }
    this.navBar.buttonStart.style.display = 'inline';

    this.navBar.buttonStart.addEventListener('click', function(){ me.startSpellCheck() }, false);
    this.navBar.buttonStop.addEventListener('click', function(){ me.stopSpellCheck() }, false);
    this.navBar.buttonOk.addEventListener('click', function(){ me.applyChanges() }, false);
    this.navBar.buttonCancel.addEventListener('click',function() { me.cancelChanges() }, false);

    document.body.appendChild(this.navBar);
  },

  createLangBox: function() {
    var me = this;
    this.langBox = document.createElement('customDiv');
    this.langBox.style = 'display:block; z-index:' + (pref.zIndex+4) + '; display:none; position:absolute; padding:0; margin:0; border:1px solid #000; background:#fff; width:auto;';
    this.langBox.langs = [];

    for (var lang=0, len=this.engine.languages.length; lang<len; lang++) {
      var o = document.createElement('customDiv');
      o.style = 'display:block; padding:0; margin:2px 4px; color:#555; font:normal 11px Tahoma, Arial, sans-serif; cursor:pointer;';
      o.value = this.engine.languages[lang];
      o.textContent = this.htmlSpecial(this.langDefs[this.engine.languages[lang]]);
      o.addEventListener('click', function(e){ OS.changeLang(e.target.value); }, false);
      this.langBox.appendChild(o);
      this.langBox.langs[o.value] = o;
    }

    this.langBox.selectedLang = '';
    this.langBox.selectLang = function(_lang) {
      if (this.selectedLang == _lang) return;
      if (this.selectedLang) {
        this.langs[this.selectedLang].style.color = '#555';
        this.langs[this.selectedLang].style.background = '#fff';
        this.langs[this.selectedLang].style.fontWeight = 'normal';
      }

      this.selectedLang = _lang;
      this.langs[_lang].style.color = '#223ead';
      this.langs[_lang].style.background = '#f3f6ff';
      this.langs[_lang].style.fontWeight = 'bold';
    }

    document.body.appendChild(this.langBox);
  },

  showLangBox: function() {
    if (!this.langBox) this.createLangBox();
    this.langBox.selectLang(this.currentLang);
    if (this.navBar)
      this.showInViewport(this.langBox, this.currentElement.pos.left + this.currentElement.pos.width - this.navBar.offsetWidth - 30, this.currentElement.pos.top + this.currentElement.pos.height +25);
    else
      this.showInViewport(this.langBox, this.currentElement.pos.left + 5, this.currentElement.pos.top + 5);
    document.addEventListener('click', this.langOutClickEvent, false);
  },

  hideLangBox: function() {
    if (this.langBox)
      this.langBox.style.display = 'none';
    document.removeEventListener('click', this.langOutClickEvent, false);
  },

  createSpellBox: function() {
    this.spellBox = document.createElement('customDiv');
    this.spellBox.style = 'z-index:' + (pref.zIndex+1) + '; box-sizing: border-box; overflow:auto; position:absolute; margin: 0; padding: 0; border: 1px solid #6A819B; background:#fff url("'+this.images.SpellBack+'") repeat-x bottom; color:#000; white-space:pre-wrap;';
    this.spellBox.style.display = 'none';
    document.body.appendChild(this.spellBox);
  },

  createSuggestBox: function() {
    var me = this;
    this.suggestBox = document.createElement('customDiv');
    this.suggestBox.style = 'display:none; z-index:' + (pref.zIndex+2) + '; margin:0; padding:1px; width:auto; height:auto; font:normal 11px Tahoma, Arial, sans-serif; position:absolute; border:1px solid #223ead; background:#f3f6ff; color:#000; text-align:left; vertical-align:top; min-width:165px;';
    this.suggestBox.addEventListener('mouseOver', function(e) {
      if (e.target == this.suggestBox) return;
      if (me.isIn(e.target.nodeName.toLowerCase(), ['li', 'div']))
        e.target.style.setProperty('background-color', '#e2eaff', 'important');
      else if (me.isIn(e.target.parentNode.nodeName.toLowerCase(), ['li', 'div']))
        e.target.parentNode.style.setProperty('background-color', '#e2eaff', 'important');
    },false);
    this.suggestBox.addEventListener('mouseOut', function(e) {
      if (e.target == this.suggestBox) return;
      if (me.isIn(e.target.nodeName.toLowerCase(), ['li', 'div']))
        e.target.style.setProperty('background-color', '#f3f6ff', 'important');
      else if (me.isIn(e.target.parentNode.nodeName.toLowerCase(), ['li', 'div']))
        e.target.parentNode.style.setProperty('background-color', '#f3f6ff', 'important');
    },false);

    var tmp = document.createElement('ul');
    this.suggestBox.appendChild(tmp);

    tmp = document.createElement('customDiv');
    tmp.style = 'padding: 1px 2px; display:block;';
    var tmp2 = document.createElement('input');
    tmp2.style = 'margin:0 5px 0 0; float:none; padding:1px 3px; width:auto; height:auto; font:inherit; color:#000; background:#fff; border:1px solid #6A819B;';
    tmp2.addEventListener('keypress', function(e) {
      if (e.keyCode == 13)
        me.replaceSuggestion(this.parentNode.parentNode.firstChild.targetElement, 0,
            this.value);
    }, false);
    tmp.appendChild(tmp2);
    var tmp2 = document.createElement('customSpan');
    tmp2.style = 'margin:0; padding:2px; font-weight:bold; color:#3A5E89; cursor:pointer;'
    tmp2.textContent = 'OK';
    tmp2.addEventListener('click', function() {
      if (this.parentNode.firstChild.value)
        me.replaceSuggestion(this.parentNode.parentNode.firstChild.targetElement, 0,
            this.parentNode.firstChild.value);
    }, false);
    tmp.appendChild(tmp2);
    this.suggestBox.appendChild(tmp);

    tmp = document.createElement('customDiv');
    tmp.style = 'display: block; padding: 1px 2px; color:#3A5E89; font-weight:bold; cursor:pointer;';
    tmp.textContent = 'Close';
    tmp.addEventListener('click', function(){ me.hideSuggestions(); }, false);
    this.suggestBox.appendChild(tmp);

    document.body.appendChild(this.suggestBox);
  },

  showNavBar: function() {
    if (!this.navBar) this.createNavBar();
    if (this.navBar.style.display != 'block') {
      this.navBar.style.display = 'block';
    }
    this.navBar.style.minWidth = (this.navBar.selectLang.offsetWidth +
      ((this.state == 2)? 50:30)) + 'px';
    this.navBar.style.width = this.navBar.style.minWidth;
    switch (pref.alignNavbar) {
      case 0:
        var leftPos = 10;
        break;
      case 1:
        var leftPos = (this.currentElement.pos.width - this.navBar.offsetWidth)/2;
        break;
      default:
        var leftPos = this.currentElement.pos.width - this.navBar.offsetWidth - 10;
        break;
    }
    this.navBar.style.left = (this.currentElement.pos.left + (leftPos>0?leftPos:10)) + 'px';
    this.navBar.style.top = (this.currentElement.pos.top +
      this.currentElement.pos.height) + 'px';
  },

  showSpellBox: function() {
    if (!this.spellBox) this.createSpellBox();
    this.spellBox.style.display = 'block';
    for (var prop in this.currentElement.pos)
      this.spellBox.style.setProperty(prop, this.currentElement.pos[prop], 'important');

    var cp = ['font-family', 'font-size', 'font-style', 'font-variant', 'font-weight', 'line-height', 'border-width', 'padding', 'text-align', 'direction'];
    var styles=window.getComputedStyle(this.currentElement, null);
    for (var i=0, prop; prop=cp[i]; i++)
      this.spellBox.style.setProperty(prop, styles.getPropertyValue(prop), 'important');
  },

  hideNavBar: function() {
    if ((this.state == 0) && this.navBar) {
      this.navBar.style.display = 'none';
    }
  },

  hideSpellBox: function() {
    if ((this.state < 2) && this.spellBox)
      this.spellBox.style.display = 'none';
  },

  placeSCElements: function(e) {
    // calls from DOMAttrModified event are delayed to not slow down page too much
    if ( e && e.type == 'DOMAttrModified' ) {
      clearTimeout(OS.globTimeout);
      OS.globTimeout = setTimeout( OS.placeSCElements, 100 );
      return;
    }
    if (OS.state > 0) {
      OS.currentElement.pos = OS.absolutePos(OS.currentElement);
      if (pref.navbarType) OS.showNavBar();
      if (OS.state > 1)
        OS.showSpellBox();
    }
  },

  showSuggestions: function(_el, _x, _y) {
    if (!this.suggestBox) this.createSuggestBox();
    this.suggestBox.replaceChild(_el.suggestions, this.suggestBox.firstChild);
    this.suggestBox.childNodes[1].firstChild.value = _el.innerText;
    this.showInViewport(this.suggestBox, _x, _y);
    document.addEventListener('click', this.suggestOutClickEvent, false);
    if (pref.enableShortcuts)
      document.addEventListener('keypress', this.suggestShortcutEvent, false);
  },

  hideSuggestions: function() {
    if (this.suggestBox) {
      this.suggestBox.style.display = 'none';
      document.removeEventListener('click', this.suggestOutClickEvent, false);
      if (pref.enableShortcuts) {
        document.removeEventListener('keypress', this.suggestShortcutEvent, false);
        //this.suggestBox.firstChild.targetElement.focus();
      }
    }
  },

  replaceSuggestion: function(_target, _number, _text) {
    var orig = (_target.innerText == _target.originalText);
    if (_text) {
      _target.innerHTML = this.htmlSpecial(_text);
      orig = (_text == _target.originalText);
    } else if (typeof(_number) == 'number') {
      if (_number == 0) {
        _target.innerHTML = this.htmlSpecial(_target.originalText);
        orig = true;
      } else if ((_number > 0) && (_number < _target.suggestions.childNodes.length) &&
            (_target.suggestions.childNodes[_number-1].suggestionNumber != -1)) {
        _target.innerHTML = _target.suggestions.childNodes[_number-1].innerHTML.replace(/^.*<\/span>/i, '');
        orig = false;
      }
    }
    if (orig) {
      _target.style.color = '#c3051b';
      _target.suggestions.lastChild.style.display = 'none';
    } else {
      _target.style.color = '#017817';
      _target.suggestions.lastChild.style.display = 'list-item';
    }
    this.replaceSelection(this.spellBox.textContent);
    this.hideSuggestions();
  },

  showInfo: function(_text, _isError) {
    var me = this;
    this.setDisplayState(4);
    this.spellBox.innerHTML = '<span style="font-weight:bold;color:'
      + (_isError? 'red':'green') + '">' + _text + '</span>';
    window.setTimeout( function(){ me.stopSpellCheck(); }, 1000);
  },


/*************************
* Internal functionality *
*************************/

  setEnabledLanguages: function() {
    if (typeof(pref.ENABLED_LANGS)!='undefined' && pref.ENABLED_LANGS.length) {
      var enabledLangs = new Array();
      for (var lang=0, len=pref.ENABLED_LANGS.length; lang<len; lang++) {
        if (this.isIn(pref.ENABLED_LANGS[lang], this.engine.languages))
          enabledLangs.push(pref.ENABLED_LANGS[lang]);
      }
      if (enabledLangs.length) this.engine.languages = enabledLangs;
    }
  },

  changeLang: function(_lang) {
    OS.hideLangBox();
    OS.currentLang = _lang;
    if (OS.navBar) {
      OS.navBar.selectLang.showLang(_lang);
      if (OS.state) OS.showNavBar();
    }
    if (OS.state > 1)
      OS.startSpellCheck();
    else {
      OS.currentElement.focus();
      OS.sendMsg(OS.messageSource, 'LANG', OS.currentLang);
    }
  },

  cycleLangs: function() {
    if ( OS.engine.languages.length < 2 ) return;

    var _langIndex = OS.isIn(OS.currentLang, OS.engine.languages);
    if (!_langIndex) return;

    var _nextLang = OS.engine.languages[ (OS.engine.languages[_langIndex]? _langIndex: 0) ];
    OS.currentLang = _nextLang;
    if (OS.navBar) {
      OS.navBar.selectLang.showLang(_nextLang);
      OS.showNavBar();
    } else {
      OS.showLangBox();
    }
    if (OS.cycleTimeout) { clearTimeout(OS.cycleTimeout); OS.cycleTimeout = null; }
    OS.cycleTimeout = setTimeout(
      function(){
        OS.changeLang(OS.currentLang);
      },
      1000
    );
  },

  getLang: function() {
    if (pref.defaultLang && this.isIn(pref.defaultLang, this.engine.languages))
      return pref.defaultLang;
    if (document.cookie) {
      var matches = document.cookie.match(new RegExp(this.cookieName + '=([^;]*)'));
      if (matches && matches[1] && this.isIn(matches[1], this.engine.languages))
        return matches[1];
    }
    if (this.isIn('en', this.engine.languages)) return 'en';
    return this.engine.languages[0];
  },

  setLang: function(_lang) {
    this.currentLang = (_lang && this.isIn(_lang, this.engine.languages)? _lang : this.getLang());
    document.cookie = this.cookieName + '=' + this.currentLang +
      '; expires=Wed, 1 Jan 2020 00:00:00 GMT;';
  },

  suggestShortcutEvent: function(e) {
    if (OS.state == 2 && OS.suggestBox.style.display == 'block') {
      var hide = false;
      if (!e.shiftKey && !e.ctrlKey && !e.altKey) {
        if (e.keyCode == 27)
          hide = true;
        else if (e.keyCode >= 48 && e.keyCode <= 57) {
          hide = true;
          OS.replaceSuggestion(OS.suggestBox.firstChild.targetElement, e.keyCode-48);
        }
      }
      if (hide) {
        document.removeEventListener('keypress', OS.suggestShortcutEvent, false);
        OS.hideSuggestions();
        e.preventDefault();
      }
    } else
      document.removeEventListener('keypress', OS.suggestShortcutEvent, false);
  },

  suggestOutClickEvent: function(e) {
    if (OS.suggestBox && OS.suggestBox.firstChild.targetElement == e.target) return;
    if (OS.state == 2 && OS.suggestBox.style.display == 'block') {
      if (!OS.isDescendant(e.target, OS.suggestBox)) {
        OS.hideSuggestions();
        document.removeEventListener('click', OS.suggestOutClickEvent, false);
      }
    } else
      document.removeEventListener('click', OS.suggestOutClickEvent, false);
  },

  langOutClickEvent: function(e) {
    if (OS.navBar && e.target == OS.navBar.selectLang) return;
    if (OS.langBox.style.display == 'block') {
      if (!OS.isDescendant(e.target, OS.langBox)) {
        OS.hideLangBox();
        if (OS.state < 2 && !OS.isDescendant(e.target, OS.currentElement))
          OS.setDisplayState(0);
        document.removeEventListener('click', OS.langOutClickEvent, false);
      }
    } else
      document.removeEventListener('click', OS.langOutClickEvent, false);
  },

  shortcutEvent: function(e) {
    if (!OS.state)
      document.removeEventListener('keypress', OS.shortcutEvent, false);
    else {
      if ((OS.state == 1 || OS.state == 2) && OS.equalKeys(pref.shortcutChangeLang, e)) {
        OS.cycleLangs();
        e.preventDefault();
      }
      if (OS.state == 1 && OS.equalKeys(pref.shortcutCheck, e)) {
        OS.startSpellCheck();
      } else if (OS.state == 2 && OS.equalKeys(pref.shortcutOK, e)) {
        OS.applyChanges();
      } else if (OS.state == 2 && OS.equalKeys(pref.shortcutCancel, e)) {
        OS.cancelChanges();
      } else if (OS.state > 2 && OS.equalKeys(pref.shortcutCancel, e)) {
        OS.stopSpellCheck();
      }
    }
  },

  setDisplayState: function(_state) {
    var me = this;
    this.state = _state;

    if (pref.navbarType) {
      if (_state) this.showNavBar();
      else window.setTimeout( function(){ me.hideNavBar() }, 100);
    }
    if (!this.engineFrame) this.createEngineFrame();
    if (_state > 1) this.showSpellBox();
    else this.hideSpellBox();
    if (_state == 3)
      this.spellBox.innerHTML = '<img src="' + this.images.Loading + '">';
    this.hideSuggestions();
    this.hideLangBox();

    if (this.navBar) {
      this.navBar.buttonStart.style.display = (_state < 2)? 'inline':'none';
      this.navBar.buttonStop.style.display = (_state > 2)? 'inline':'none';
      this.navBar.buttonOk.style.display = (_state == 2)? 'inline':'none';
      this.navBar.buttonCancel.style.display = (_state == 2)? 'inline':'none';
    }
    if (pref.enableShortcuts) {
      if (_state) document.addEventListener('keypress', this.shortcutEvent, false);
      else document.removeEventListener('keypress', this.shortcutEvent, false);
    }
  },

  startSpellCheck: function() {
    if (!this.currentElement) return;

    this.selection.text = '';
    this.currentElement.focus();
    if (document.selection) {
      var sel = document.selection.createRange();
      if (sel.text != '') {
        this.selection.text = sel.text;
        sel.text = '\x01';
        var pos = this.currentElement.value.indexOf('\x01');
        this.selection.prefix = this.currentElement.value.slice(0, pos);
        this.selection.postfix = this.currentElement.value.slice(pos+1);
        this.replaceSelection(this.selection.text);
      }
    }
    if (this.selection.text == '') {
      this.selection.text = this.currentElement.value;
      this.selection.prefix = '';
      this.selection.postfix = '';
    }
    if (this.selection.text == '') {
      this.showInfo('No spelling errors found');
      return;
    }

    this.setDisplayState(3);
    this.sendMsg(this.messageSource, this.currentLang, this.selection.text);
  },

  stopSpellCheck: function() {
    this.sendMsg(this.messageSource, 'STOP', '');
    this.currentElement.focus();
    this.setDisplayState(1);
  },

  parseResult: function(_text) {
    var me = this, res=[];

    if (this.engine.gmailType) { // ------------ gmail response
      if ( _text.indexOf('[')==-1 )
        return 'Please login to GMail first';

      try {
        matches = eval(_text);
        if (!!matches[0]) return 'Server data error';

        matches = matches[3];
        for (var i=matches.length-1; i>=0; i--)
          res[i] = {'w': matches[i][0], 'l': matches[i][0].length, 'm': matches[i][1]};

        var getPos = function(_t, _tSel) {
          var pos = [], i = -1;
          while ((i = _tSel.indexOf(_t, i+1)) != -1) {
            if ( (!i || (_tSel[i-1].search(/[A-Za-z]/) == -1)) && ((i == _tSel.length - _t.length) || (_tSel[i+_t.length].search(/[A-Za-z]/) == -1)) )
              pos.push(i);
          }
          if (pos.length == 0) pos.push(_tSel.indexOf(_t));
          return pos;
        }
        for (var i=res.length-1; i>=0; i--) {
          matches = getPos(res[i].w, me.selection.text);
          res[i].o = matches[0];
          for (var j=1; j<matches.length; j++) {
            res.push({'w': res[i].w, 'l': res[i].l, 'm': res[i].m, 'o': matches[j]});
          }
        }
        res.sort(function(a, b) {
          if (a.o == b.o) return (a.l > b.l)? 1:-1;
          if (a.o > b.o) return 1;
          return -1;
        });
        for (var i=0; i<res.length-1; i++)
          if (res[i].o == res[i+1].o) {
            res.splice(i,1);
            i--;
          }
      } catch (e) {
        e.message = 'OSpell '+'1 '+pref.ENGINE+'\n'+e.message;
        opera.postError(e);
        return 'Parse error';
      }

    } else { // ------------------ orangoo response

      var matches = _text.match(/<\?xml[\s\S]*?(?:<\/spellresult>|\/>)/);
      if (!matches) return 'Invalid response from server';
      _text = matches[0];

      try {

        var parser = new DOMParser();
        parser = parser.parseFromString(_text, 'text/xml');

        if ( !parser.selectSingleNode('/spellresult') )
          return 'Invalid response from server';
        //~ if (parser.documentElement.getAttribute('error') != '0')
          //~ return 'Server data error';
        matches = parser.selectNodes('//c');

        for (var i=0; i<matches.length; i++) {
          res[i] = {
            'o': parseInt(matches[i].getAttribute('o')),
            'l': parseInt(matches[i].getAttribute('l')),
            'm': matches[i].textContent.split('\t')
          };
          res[i].w = this.selection.text.substr(res[i].o, res[i].l);
        }

      } catch (e) {
        e.message = 'OSpell '+'2 '+pref.ENGINE+'\n'+e.message;
        opera.postError(e);
        return 'Parse error';
      }

    }

    return res;
  },

  displayResult: function(_res) {
    var me = this;
    if (this.state != 3) return;

    var text = this.selection.text;
    var inner = '';
    var pos = 0;
    for (var i=0; i<_res.length; i++) {
      inner += this.htmlSpecial(text.slice(pos,_res[i].o)) + '<span>' +
        this.htmlSpecial(text.substr(_res[i].o, _res[i].l)) + '</span>';
      pos = _res[i].o + _res[i].l;
    }
    this.spellBox.innerHTML = inner + this.htmlSpecial(text.slice(pos));

    inner = this.spellBox.selectNodes('span');
    for (var i=0,el; el=inner[i]; i++) {

      el.style = 'text-decoration:underline; cursor:pointer; color:#c3051b;';
      el.originalText = _res[i].w;
      el.addEventListener('click', function(e) {
        me.showSuggestions(this, me.currentElement.pos.left + this.offsetLeft,
          me.currentElement.pos.top + this.offsetTop + 17);
      }, false);

      var ul = document.createElement('ul');
      ul.style = 'list-style:none; margin:0; padding:0; font:inherit; text-align:left; color:#000;';
      ul.targetElement = el;

      var li, m = _res[i].m;
      if (m && m.length && m[0]) {
        for (var j=0; j<m.length; j++) {
          li = document.createElement('li');
          li.innerHTML = '<span style="color:#444; margin-right:7px; font-size:10px;">' + ((j<9) ? j+1:' ') + '</span>' + this.htmlSpecial(m[j]);
          li.style = 'display:list-item; margin:0; padding:1px 2px; font:inherit; text-align:left; cursor:pointer;';
          li.suggestionNumber = j+1;
          li.addEventListener('click', function() {
            me.replaceSuggestion(this.parentNode.targetElement, this.suggestionNumber);
          }, false);
          ul.appendChild(li);
        }
      } else {
          li = document.createElement('li');
          li.innerHTML = 'No suggestions...';
          li.style = 'display:list-item; margin:0; padding:1px 2px; font:inherit; text-align:left; font-style:italic;';
          li.suggestionNumber = -1;
          ul.appendChild(li);
      }

      li = document.createElement('li');
      li.style = 'display:list-item; margin:0; padding:1px 2px; font:inherit; text-align:left; color:#c3051b; cursor:pointer;';
      li.style.display = 'none';
      li.innerHTML = '<span style="color:#c3737e; margin-right:7px; font-size:10px;">0</span>Revert: ' + this.htmlSpecial(_res[i].w);
      li.suggestionNumber = 0;
      li.addEventListener('click', function(e) {
        me.replaceSuggestion(this.parentNode.targetElement, 0);
      }, false);
      ul.appendChild(li);

      el.suggestions = ul;
    }
  },

  applyChanges: function() {
    this.replaceSelection(this.spellBox.textContent);
    this.stopSpellCheck();
  },

  cancelChanges: function() {
    if (pref.confirmCancel && !window.confirm('Cancel?')) return;
    this.replaceSelection(this.selection.text);
    this.stopSpellCheck();
  },

  replaceSelection: function(_text) {
    if (!this.currentElement) return;
    this.currentElement.value = this.selection.prefix + _text +
        this.selection.postfix;
  }

};
OS.init();

})();
