]> git.donarmstrong.com Git - roundcube.git/blob - program/js/googiespell.js.src
Imported Upstream version 0.7
[roundcube.git] / program / js / googiespell.js.src
1 /*
2  SpellCheck
3     jQuery'fied spell checker based on GoogieSpell 4.0
4       (which was published under GPL "version 2 or any later version")
5
6  Copyright (C) 2006 Amir Salihefendic
7  Copyright (C) 2009 Aleksander Machniak
8  Copyright (C) 2011 Kolab Systems AG
9      LICENSE
10          GPL
11      AUTHORS
12          4mir Salihefendic (http://amix.dk) - amix@amix.dk
13             Aleksander Machniak - alec [at] alec.pl
14 */
15
16 var GOOGIE_CUR_LANG,
17     GOOGIE_DEFAULT_LANG = 'en';
18
19 function GoogieSpell(img_dir, server_url, has_dict)
20 {
21     var ref = this,
22         cookie_value = getCookie('language');
23
24     GOOGIE_CUR_LANG = cookie_value != null ? cookie_value : GOOGIE_DEFAULT_LANG;
25
26     this.array_keys = function(arr) {
27             var res = [];
28             for (var key in arr) { res.push([key]); }
29             return res;
30     }
31
32     this.img_dir = img_dir;
33     this.server_url = server_url;
34
35     this.org_lang_to_word = {
36             "da": "Dansk", "de": "Deutsch", "en": "English",
37         "es": "Español", "fr": "Français", "it": "Italiano", 
38         "nl": "Nederlands", "pl": "Polski", "pt": "Português",
39         "fi": "Suomi", "sv": "Svenska"
40     };
41     this.lang_to_word = this.org_lang_to_word;
42     this.langlist_codes = this.array_keys(this.lang_to_word);
43     this.show_change_lang_pic = true;
44     this.change_lang_pic_placement = 'right';
45     this.report_state_change = true;
46
47     this.ta_scroll_top = 0;
48     this.el_scroll_top = 0;
49
50     this.lang_chck_spell = "Check spelling";
51     this.lang_revert = "Revert to";
52     this.lang_close = "Close";
53     this.lang_rsm_edt = "Resume editing";
54     this.lang_no_error_found = "No spelling errors found";
55     this.lang_no_suggestions = "No suggestions";
56     this.lang_learn_word = "Add to dictionary";
57
58     this.show_spell_img = false; // roundcube mod.
59     this.decoration = true;
60     this.use_close_btn = false;
61     this.edit_layer_dbl_click = true;
62     this.report_ta_not_found = true;
63
64     // Extensions
65     this.custom_ajax_error = null;
66     this.custom_no_spelling_error = null;
67     this.custom_menu_builder = []; // Should take an eval function and a build menu function
68     this.custom_item_evaulator = null; // Should take an eval function and a build menu function
69     this.extra_menu_items = [];
70     this.custom_spellcheck_starter = null;
71     this.main_controller = true;
72     this.has_dictionary = has_dict;
73
74     // Observers
75     this.lang_state_observer = null;
76     this.spelling_state_observer = null;
77     this.show_menu_observer = null;
78     this.all_errors_fixed_observer = null;
79
80     // Focus links - used to give the text box focus
81     this.use_focus = false;
82     this.focus_link_t = null;
83     this.focus_link_b = null;
84
85     // Counters
86     this.cnt_errors = 0;
87     this.cnt_errors_fixed = 0;
88
89     // Set document's onclick to hide the language and error menu
90     $(document).bind('click', function(e) {
91         var target = $(e.target);
92         if(target.attr('googie_action_btn') != '1' && ref.isLangWindowShown())
93                 ref.hideLangWindow();
94             if(target.attr('googie_action_btn') != '1' && ref.isErrorWindowShown())
95             ref.hideErrorWindow();
96     });
97
98
99 this.decorateTextarea = function(id)
100 {
101     this.text_area = typeof id === 'string' ? document.getElementById(id) : id;
102
103     if (this.text_area) {
104         if (!this.spell_container && this.decoration) {
105             var table = document.createElement('table'),
106                 tbody = document.createElement('tbody'),
107                 tr = document.createElement('tr'),
108                 spell_container = document.createElement('td'),
109                 r_width = this.isDefined(this.force_width) ? this.force_width : this.text_area.offsetWidth,
110                 r_height = this.isDefined(this.force_height) ? this.force_height : 16;
111
112             tr.appendChild(spell_container);
113             tbody.appendChild(tr);
114             $(table).append(tbody).insertBefore(this.text_area).width('100%').height(r_height);
115             $(spell_container).height(r_height).width(r_width).css('text-align', 'right');
116
117             this.spell_container = spell_container;
118         }
119
120         this.checkSpellingState();
121     }
122     else if (this.report_ta_not_found)
123         alert('Text area not found');
124 };
125
126 //////
127 // API Functions (the ones that you can call)
128 /////
129 this.setSpellContainer = function(id)
130 {
131     this.spell_container = typeof id === 'string' ? document.getElementById(id) : id;
132 };
133
134 this.setLanguages = function(lang_dict)
135 {
136     this.lang_to_word = lang_dict;
137     this.langlist_codes = this.array_keys(lang_dict);
138 };
139
140 this.setCurrentLanguage = function(lan_code)
141 {
142     GOOGIE_CUR_LANG = lan_code;
143
144     //Set cookie
145     var now = new Date();
146     now.setTime(now.getTime() + 365 * 24 * 60 * 60 * 1000);
147     setCookie('language', lan_code, now);
148 };
149
150 this.setForceWidthHeight = function(width, height)
151 {
152     // Set to null if you want to use one of them
153     this.force_width = width;
154     this.force_height = height;
155 };
156
157 this.setDecoration = function(bool)
158 {
159     this.decoration = bool;
160 };
161
162 this.dontUseCloseButtons = function()
163 {
164     this.use_close_btn = false;
165 };
166
167 this.appendNewMenuItem = function(name, call_back_fn, checker)
168 {
169     this.extra_menu_items.push([name, call_back_fn, checker]);
170 };
171
172 this.appendCustomMenuBuilder = function(eval_fn, builder)
173 {
174     this.custom_menu_builder.push([eval_fn, builder]);
175 };
176
177 this.setFocus = function()
178 {
179     try {
180         this.focus_link_b.focus();
181         this.focus_link_t.focus();
182         return true;
183     }
184     catch(e) {
185         return false;
186     }
187 };
188
189
190 //////
191 // Set functions (internal)
192 /////
193 this.setStateChanged = function(current_state)
194 {
195     this.state = current_state;
196     if (this.spelling_state_observer != null && this.report_state_change)
197         this.spelling_state_observer(current_state, this);
198 };
199
200 this.setReportStateChange = function(bool)
201 {
202     this.report_state_change = bool;
203 };
204
205
206 //////
207 // Request functions
208 /////
209 this.getUrl = function()
210 {
211     return this.server_url + GOOGIE_CUR_LANG;
212 };
213
214 this.escapeSpecial = function(val)
215 {
216     return val ? val.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") : '';
217 };
218
219 this.createXMLReq = function (text)
220 {
221     return '<?xml version="1.0" encoding="utf-8" ?>'
222         + '<spellrequest textalreadyclipped="0" ignoredups="0" ignoredigits="1" ignoreallcaps="1">'
223         + '<text>' + text + '</text></spellrequest>';
224 };
225
226 this.spellCheck = function(ignore)
227 {
228     this.prepare(ignore);
229
230     var req_text = this.escapeSpecial(this.orginal_text),
231         ref = this;
232
233     $.ajax({ type: 'POST', url: this.getUrl(), data: this.createXMLReq(req_text), dataType: 'text',
234             error: function(o) {
235             if (ref.custom_ajax_error)
236                     ref.custom_ajax_error(ref);
237             else
238                     alert('An error was encountered on the server. Please try again later.');
239             if (ref.main_controller) {
240                     $(ref.spell_span).remove();
241                     ref.removeIndicator();
242             }
243             ref.checkSpellingState();
244             },
245         success: function(data) {
246             ref.processData(data);
247             if (!ref.results.length) {
248                     if (!ref.custom_no_spelling_error)
249                         ref.flashNoSpellingErrorState();
250                 else
251                         ref.custom_no_spelling_error(ref);
252             }
253             ref.removeIndicator();
254             }
255     });
256 };
257
258 this.learnWord = function(word, id)
259 {
260     word = this.escapeSpecial(word.innerHTML);
261
262     var ref = this,
263         req_text = '<?xml version="1.0" encoding="utf-8" ?><learnword><text>' + word + '</text></learnword>';
264
265     $.ajax({ type: 'POST', url: this.getUrl(), data: req_text, dataType: 'text',
266             error: function(o) {
267             if (ref.custom_ajax_error)
268                     ref.custom_ajax_error(ref);
269             else
270                     alert('An error was encountered on the server. Please try again later.');
271             },
272         success: function(data) {
273             }
274     });
275 };
276
277
278 //////
279 // Spell checking functions
280 /////
281 this.prepare = function(ignore, no_indicator)
282 {
283     this.cnt_errors_fixed = 0;
284     this.cnt_errors = 0;
285     this.setStateChanged('checking_spell');
286
287     if (!no_indicator && this.main_controller)
288         this.appendIndicator(this.spell_span);
289
290     this.error_links = [];
291     this.ta_scroll_top = this.text_area.scrollTop;
292     this.ignore = ignore;
293     this.hideLangWindow();
294
295     if ($(this.text_area).val() == '' || ignore) {
296         if (!this.custom_no_spelling_error)
297             this.flashNoSpellingErrorState();
298         else
299             this.custom_no_spelling_error(this);
300         this.removeIndicator();
301         return;
302     }
303
304     this.createEditLayer(this.text_area.offsetWidth, this.text_area.offsetHeight);
305     this.createErrorWindow();
306     $('body').append(this.error_window);
307
308     try { netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); }
309     catch (e) { }
310
311     if (this.main_controller)
312         $(this.spell_span).unbind('click');
313
314     this.orginal_text = $(this.text_area).val();
315 };
316
317 this.parseResult = function(r_text)
318 {
319     // Returns an array: result[item] -> ['attrs'], ['suggestions']
320     var re_split_attr_c = /\w+="(\d+|true)"/g,
321         re_split_text = /\t/g,
322         matched_c = r_text.match(/<c[^>]*>[^<]*<\/c>/g),
323         results = [];
324
325     if (matched_c == null)
326         return results;
327
328     for (var i=0, len=matched_c.length; i < len; i++) {
329         var item = [];
330         this.errorFound();
331
332         // Get attributes
333         item['attrs'] = [];
334         var c_attr, val,
335             split_c = matched_c[i].match(re_split_attr_c);
336         for (var j=0; j < split_c.length; j++) {
337             c_attr = split_c[j].split(/=/);
338             val = c_attr[1].replace(/"/g, '');
339             item['attrs'][c_attr[0]] = val != 'true' ? parseInt(val) : val;
340         }
341
342         // Get suggestions
343         item['suggestions'] = [];
344         var only_text = matched_c[i].replace(/<[^>]*>/g, ''),
345             split_t = only_text.split(re_split_text);
346         for (var k=0; k < split_t.length; k++) {
347             if(split_t[k] != '')
348                 item['suggestions'].push(split_t[k]);
349         }
350         results.push(item);
351     }
352
353     return results;
354 };
355
356 this.processData = function(data)
357 {
358     this.results = this.parseResult(data);
359     if (this.results.length) {
360             this.showErrorsInIframe();
361             this.resumeEditingState();
362     }
363 };
364
365 //////
366 // Error menu functions
367 /////
368 this.createErrorWindow = function()
369 {
370     this.error_window = document.createElement('div');
371     $(this.error_window).addClass('googie_window popupmenu').attr('googie_action_btn', '1');
372 };
373
374 this.isErrorWindowShown = function()
375 {
376     return $(this.error_window).is(':visible');
377 };
378
379 this.hideErrorWindow = function()
380 {
381     $(this.error_window).hide();
382     $(this.error_window_iframe).hide();
383 };
384
385 this.updateOrginalText = function(offset, old_value, new_value, id)
386 {
387     var part_1 = this.orginal_text.substring(0, offset),
388         part_2 = this.orginal_text.substring(offset+old_value.length),
389         add_2_offset = new_value.length - old_value.length;
390
391     this.orginal_text = part_1 + new_value + part_2;
392     $(this.text_area).val(this.orginal_text);
393     for (var j=0, len=this.results.length; j<len; j++) {
394         // Don't edit the offset of the current item
395         if (j != id && j > id)
396             this.results[j]['attrs']['o'] += add_2_offset;
397     }
398 };
399
400 this.saveOldValue = function(elm, old_value) {
401     elm.is_changed = true;
402     elm.old_value = old_value;
403 };
404
405 this.createListSeparator = function()
406 {
407     var td = document.createElement('td'),
408         tr = document.createElement('tr');
409
410     $(td).html(' ').attr('googie_action_btn', '1')
411             .css({'cursor': 'default', 'font-size': '3px', 'border-top': '1px solid #ccc', 'padding-top': '3px'});
412     tr.appendChild(td);
413
414     return tr;
415 };
416
417 this.correctError = function(id, elm, l_elm, rm_pre_space)
418 {
419     var old_value = elm.innerHTML,
420         new_value = l_elm.nodeType == 3 ? l_elm.nodeValue : l_elm.innerHTML,
421         offset = this.results[id]['attrs']['o'];
422
423     if (rm_pre_space) {
424         var pre_length = elm.previousSibling.innerHTML;
425         elm.previousSibling.innerHTML = pre_length.slice(0, pre_length.length-1);
426         old_value = " " + old_value;
427         offset--;
428     }
429
430     this.hideErrorWindow();
431     this.updateOrginalText(offset, old_value, new_value, id);
432
433     $(elm).html(new_value).css('color', 'green').attr('is_corrected', true);
434
435     this.results[id]['attrs']['l'] = new_value.length;
436
437     if (!this.isDefined(elm.old_value))
438         this.saveOldValue(elm, old_value);
439
440     this.errorFixed();
441 };
442
443 this.ignoreError = function(elm, id)
444 {
445     // @TODO: ignore all same words
446     $(elm).removeAttr('class').css('color', '').unbind();
447     this.hideErrorWindow();
448 };
449
450 this.showErrorWindow = function(elm, id)
451 {
452     if (this.show_menu_observer)
453         this.show_menu_observer(this);
454
455     var ref = this,
456         pos = $(elm).offset(),
457         table = document.createElement('table'),
458         list = document.createElement('tbody');
459
460     $(this.error_window).html('');
461     $(table).addClass('googie_list').attr('googie_action_btn', '1');
462
463     // Check if we should use custom menu builder, if not we use the default
464     var changed = false;
465     for (var k=0; k<this.custom_menu_builder.length; k++) {
466         var eb = this.custom_menu_builder[k];
467         if (eb[0](this.results[id])) {
468             changed = eb[1](this, list, elm);
469             break;
470         }
471     }
472
473     if (!changed) {
474         // Build up the result list
475         var suggestions = this.results[id]['suggestions'],
476             offset = this.results[id]['attrs']['o'],
477             len = this.results[id]['attrs']['l'],
478             row, item, dummy;
479
480         // [Add to dictionary] button
481         if (this.has_dictionary && !$(elm).attr('is_corrected')) {
482             row = document.createElement('tr'),
483             item = document.createElement('td'),
484             dummy = document.createElement('span');
485
486             $(dummy).text(this.lang_learn_word);
487             $(item).attr('googie_action_btn', '1').css('cursor', 'default')
488                 .mouseover(ref.item_onmouseover)
489                 .mouseout(ref.item_onmouseout)
490                             .click(function(e) {
491                                 ref.learnWord(elm, id);
492                                 ref.ignoreError(elm, id);
493                             });
494
495             item.appendChild(dummy);
496             row.appendChild(item);
497             list.appendChild(row);
498         }
499 /*
500         if (suggestions.length == 0) {
501             row = document.createElement('tr'),
502             item = document.createElement('td'),
503             dummy = document.createElement('span');
504
505             $(dummy).text(this.lang_no_suggestions);
506             $(item).attr('googie_action_btn', '1').css('cursor', 'default');
507
508             item.appendChild(dummy);
509             row.appendChild(item);
510             list.appendChild(row);
511         }
512 */
513         for (var i=0, len=suggestions.length; i < len; i++) {
514             row = document.createElement('tr'),
515             item = document.createElement('td'),
516             dummy = document.createElement('span');
517
518             $(dummy).html(suggestions[i]);
519
520             $(item).mouseover(this.item_onmouseover).mouseout(this.item_onmouseout)
521                     .click(function(e) { ref.correctError(id, elm, e.target.firstChild) });
522
523             item.appendChild(dummy);
524             row.appendChild(item);
525             list.appendChild(row);
526         }
527
528         // The element is changed, append the revert
529         if (elm.is_changed && elm.innerHTML != elm.old_value) {
530             var old_value = elm.old_value,
531                 revert_row = document.createElement('tr'),
532                 revert = document.createElement('td'),
533                 rev_span = document.createElement('span');
534
535                 $(rev_span).addClass('googie_list_revert').html(this.lang_revert + ' ' + old_value);
536
537             $(revert).mouseover(this.item_onmouseover).mouseout(this.item_onmouseout)
538                     .click(function(e) {
539                     ref.updateOrginalText(offset, elm.innerHTML, old_value, id);
540                     $(elm).removeAttr('is_corrected').css('color', '#b91414').html(old_value);
541                     ref.hideErrorWindow();
542                     });
543
544             revert.appendChild(rev_span);
545             revert_row.appendChild(revert);
546             list.appendChild(revert_row);
547         }
548
549         // Append the edit box
550         var edit_row = document.createElement('tr'),
551             edit = document.createElement('td'),
552             edit_input = document.createElement('input'),
553             ok_pic = document.createElement('img'),
554                 edit_form = document.createElement('form');
555
556         var onsub = function () {
557             if (edit_input.value != '') {
558                 if (!ref.isDefined(elm.old_value))
559                     ref.saveOldValue(elm, elm.innerHTML);
560
561                 ref.updateOrginalText(offset, elm.innerHTML, edit_input.value, id);
562                         $(elm).attr('is_corrected', true).css('color', 'green').html(edit_input.value);
563                 ref.hideErrorWindow();
564             }
565             return false;
566         };
567
568             $(edit_input).width(120).css({'margin': 0, 'padding': 0});
569             $(edit_input).val(elm.innerHTML).attr('googie_action_btn', '1');
570             $(edit).css('cursor', 'default').attr('googie_action_btn', '1');
571
572             $(ok_pic).attr('src', this.img_dir + 'ok.gif')
573                 .width(32).height(16)
574             .css({'cursor': 'pointer', 'margin-left': '2px', 'margin-right': '2px'})
575                 .click(onsub);
576
577         $(edit_form).attr('googie_action_btn', '1')
578                 .css({'margin': 0, 'padding': 0, 'cursor': 'default', 'white-space': 'nowrap'})
579                 .submit(onsub);
580
581             edit_form.appendChild(edit_input);
582             edit_form.appendChild(ok_pic);
583         edit.appendChild(edit_form);
584         edit_row.appendChild(edit);
585         list.appendChild(edit_row);
586
587         // Append extra menu items
588         if (this.extra_menu_items.length > 0)
589                 list.appendChild(this.createListSeparator());
590
591         var loop = function(i) {
592             if (i < ref.extra_menu_items.length) {
593                 var e_elm = ref.extra_menu_items[i];
594
595                 if (!e_elm[2] || e_elm[2](elm, ref)) {
596                     var e_row = document.createElement('tr'),
597                       e_col = document.createElement('td');
598
599                                 $(e_col).html(e_elm[0])
600                         .mouseover(ref.item_onmouseover)
601                         .mouseout(ref.item_onmouseout)
602                                     .click(function() { return e_elm[1](elm, ref) });
603
604                                 e_row.appendChild(e_col);
605                     list.appendChild(e_row);
606                 }
607                 loop(i+1);
608             }
609         };
610
611         loop(0);
612         loop = null;
613
614         //Close button
615         if (this.use_close_btn) {
616             list.appendChild(this.createCloseButton(this.hideErrorWindow));
617         }
618     }
619
620     table.appendChild(list);
621     this.error_window.appendChild(table);
622
623     // calculate and set position
624     var height = $(this.error_window).height(),
625         width = $(this.error_window).width(),
626         pageheight = $(document).height(),
627         pagewidth = $(document).width(),
628         top = pos.top + height + 20 < pageheight ? pos.top + 20 : pos.top - height,
629         left = pos.left + width < pagewidth ? pos.left : pos.left - width;
630
631     $(this.error_window).css({'top': top+'px', 'left': left+'px'}).show();
632
633     // Dummy for IE - dropdown bug fix
634     if ($.browser.msie) {
635             if (!this.error_window_iframe) {
636             var iframe = $('<iframe>').css({'position': 'absolute', 'z-index': -1});
637                 $('body').append(iframe);
638             this.error_window_iframe = iframe;
639         }
640
641             $(this.error_window_iframe)
642                 .css({'top': this.error_window.offsetTop, 'left': this.error_window.offsetLeft,
643                 'width': this.error_window.offsetWidth, 'height': this.error_window.offsetHeight})
644             .show();
645     }
646 };
647
648
649 //////
650 // Edit layer (the layer where the suggestions are stored)
651 //////
652 this.createEditLayer = function(width, height)
653 {
654     this.edit_layer = document.createElement('div');
655     $(this.edit_layer).addClass('googie_edit_layer').attr('id', 'googie_edit_layer')
656         .width('auto').height(height);
657
658     if (this.text_area.nodeName.toLowerCase() != 'input' || $(this.text_area).val() == '') {
659         $(this.edit_layer).css('overflow', 'auto').height(height-4);
660     } else {
661         $(this.edit_layer).css('overflow', 'hidden');
662     }
663
664     var ref = this;
665
666     if (this.edit_layer_dbl_click) {
667         $(this.edit_layer).dblclick(function(e) {
668             if (e.target.className != 'googie_link' && !ref.isErrorWindowShown()) {
669                 ref.resumeEditing();
670                 var fn1 = function() {
671                     $(ref.text_area).focus();
672                     fn1 = null;
673                 };
674                 window.setTimeout(fn1, 10);
675             }
676             return false;
677         });
678     }
679 };
680
681 this.resumeEditing = function()
682 {
683     this.setStateChanged('ready');
684
685     if (this.edit_layer)
686         this.el_scroll_top = this.edit_layer.scrollTop;
687
688     this.hideErrorWindow();
689
690     if (this.main_controller)
691         $(this.spell_span).removeClass().addClass('googie_no_style');
692
693     if (!this.ignore) {
694         if (this.use_focus) {
695             $(this.focus_link_t).remove();
696             $(this.focus_link_b).remove();
697         }
698
699         $(this.edit_layer).remove();
700         $(this.text_area).show();
701
702         if (this.el_scroll_top != undefined)
703             this.text_area.scrollTop = this.el_scroll_top;
704     }
705     this.checkSpellingState(false);
706 };
707
708 this.createErrorLink = function(text, id)
709 {
710     var elm = document.createElement('span'),
711         ref = this,
712         d = function (e) {
713             ref.showErrorWindow(elm, id);
714             d = null;
715             return false;
716         };
717
718     $(elm).html(text).addClass('googie_link').click(d).removeAttr('is_corrected')
719             .attr({'googie_action_btn' : '1', 'g_id' : id});
720
721     return elm;
722 };
723
724 this.createPart = function(txt_part)
725 {
726     if (txt_part == " ")
727         return document.createTextNode(" ");
728
729     txt_part = this.escapeSpecial(txt_part);
730     txt_part = txt_part.replace(/\n/g, "<br>");
731     txt_part = txt_part.replace(/    /g, " &nbsp;");
732     txt_part = txt_part.replace(/^ /g, "&nbsp;");
733     txt_part = txt_part.replace(/ $/g, "&nbsp;");
734
735     var span = document.createElement('span');
736     $(span).html(txt_part);
737     return span;
738 };
739
740 this.showErrorsInIframe = function()
741 {
742     var output = document.createElement('div'),
743         pointer = 0,
744         results = this.results;
745
746     if (results.length > 0) {
747         for (var i=0, length=results.length; i < length; i++) {
748             var offset = results[i]['attrs']['o'],
749                 len = results[i]['attrs']['l'],
750                 part_1_text = this.orginal_text.substring(pointer, offset),
751                 part_1 = this.createPart(part_1_text);
752
753             output.appendChild(part_1);
754             pointer += offset - pointer;
755
756             // If the last child was an error, then insert some space
757             var err_link = this.createErrorLink(this.orginal_text.substr(offset, len), i);
758             this.error_links.push(err_link);
759             output.appendChild(err_link);
760             pointer += len;
761         }
762
763         // Insert the rest of the orginal text
764         var part_2_text = this.orginal_text.substr(pointer, this.orginal_text.length),
765             part_2 = this.createPart(part_2_text);
766
767         output.appendChild(part_2);
768     }
769     else
770         output.innerHTML = this.orginal_text;
771
772     $(output).css('text-align', 'left');
773
774     var me = this;
775     if (this.custom_item_evaulator)
776         $.map(this.error_links, function(elm){me.custom_item_evaulator(me, elm)});
777
778     $(this.edit_layer).append(output);
779
780     // Hide text area and show edit layer
781     $(this.text_area).hide();
782     $(this.edit_layer).insertBefore(this.text_area);
783
784     if (this.use_focus) {
785         this.focus_link_t = this.createFocusLink('focus_t');
786         this.focus_link_b = this.createFocusLink('focus_b');
787
788         $(this.focus_link_t).insertBefore(this.edit_layer);
789         $(this.focus_link_b).insertAfter(this.edit_layer);
790     }
791
792 //    this.edit_layer.scrollTop = this.ta_scroll_top;
793 };
794
795
796 //////
797 // Choose language menu
798 //////
799 this.createLangWindow = function()
800 {
801     this.language_window = document.createElement('div');
802     $(this.language_window).addClass('googie_window popupmenu')
803             .width(100).attr('googie_action_btn', '1');
804
805     // Build up the result list
806     var table = document.createElement('table'),
807         list = document.createElement('tbody'),
808         ref = this,
809         row, item, span;
810
811     $(table).addClass('googie_list').width('100%');
812     this.lang_elms = [];
813
814     for (i=0; i < this.langlist_codes.length; i++) {
815         row = document.createElement('tr');
816         item = document.createElement('td');
817         span = document.createElement('span');
818
819             $(span).text(this.lang_to_word[this.langlist_codes[i]]);
820         this.lang_elms.push(item);
821
822         $(item).attr('googieId', this.langlist_codes[i])
823             .bind('click', function(e) {
824                     ref.deHighlightCurSel();
825                     ref.setCurrentLanguage($(this).attr('googieId'));
826
827                     if (ref.lang_state_observer != null) {
828                     ref.lang_state_observer();
829                     }
830
831                     ref.highlightCurSel();
832                     ref.hideLangWindow();
833             })
834             .bind('mouseover', function(e) {
835                     if (this.className != "googie_list_selected")
836                     this.className = "googie_list_onhover";
837             })
838             .bind('mouseout', function(e) {
839                     if (this.className != "googie_list_selected")
840                     this.className = "googie_list_onout";
841             });
842
843             item.appendChild(span);
844         row.appendChild(item);
845         list.appendChild(row);
846     }
847
848     // Close button
849     if (this.use_close_btn) {
850         list.appendChild(this.createCloseButton(function () { ref.hideLangWindow.apply(ref) }));
851     }
852
853     this.highlightCurSel();
854
855     table.appendChild(list);
856     this.language_window.appendChild(table);
857 };
858
859 this.isLangWindowShown = function()
860 {
861     return $(this.language_window).is(':visible');
862 };
863
864 this.hideLangWindow = function()
865 {
866     $(this.language_window).hide();
867     $(this.switch_lan_pic).removeClass().addClass('googie_lang_3d_on');
868 };
869
870 this.showLangWindow = function(elm)
871 {
872     if (this.show_menu_observer)
873         this.show_menu_observer(this);
874
875     this.createLangWindow();
876     $('body').append(this.language_window);
877
878     var pos = $(elm).offset(),
879         height = $(elm).height(),
880         width = $(elm).width(),
881         h = $(this.language_window).height(),
882         pageheight = $(document).height(),
883         left = this.change_lang_pic_placement == 'right' ?
884                 pos.left - 100 + width : pos.left + width,
885         top = pos.top + h < pageheight ? pos.top + height : pos.top - h - 4;
886
887     $(this.language_window).css({'top' : top+'px','left' : left+'px'}).show();
888
889     this.highlightCurSel();
890 };
891
892 this.deHighlightCurSel = function()
893 {
894     $(this.lang_cur_elm).removeClass().addClass('googie_list_onout');
895 };
896
897 this.highlightCurSel = function()
898 {
899     if (GOOGIE_CUR_LANG == null)
900         GOOGIE_CUR_LANG = GOOGIE_DEFAULT_LANG;
901     for (var i=0; i < this.lang_elms.length; i++) {
902         if ($(this.lang_elms[i]).attr('googieId') == GOOGIE_CUR_LANG) {
903             this.lang_elms[i].className = 'googie_list_selected';
904             this.lang_cur_elm = this.lang_elms[i];
905         }
906         else {
907             this.lang_elms[i].className = 'googie_list_onout';
908         }
909     }
910 };
911
912 this.createChangeLangPic = function()
913 {
914     var img = $('<img>')
915             .attr({src: this.img_dir + 'change_lang.gif', 'alt': 'Change language', 'googie_action_btn': '1'}),
916         switch_lan = document.createElement('span');
917         ref = this;
918
919     $(switch_lan).addClass('googie_lang_3d_on')
920             .append(img)
921             .bind('click', function(e) {
922             var elm = this.tagName.toLowerCase() == 'img' ? this.parentNode : this;
923             if($(elm).hasClass('googie_lang_3d_click')) {
924                 elm.className = 'googie_lang_3d_on';
925                     ref.hideLangWindow();
926             }
927             else {
928                     elm.className = 'googie_lang_3d_click';
929                     ref.showLangWindow(elm);
930             }
931             });
932
933     return switch_lan;
934 };
935
936 this.createSpellDiv = function()
937 {
938     var span = document.createElement('span');
939
940     $(span).addClass('googie_check_spelling_link').text(this.lang_chck_spell);
941
942     if (this.show_spell_img) {
943             $(span).append(' ').append($('<img>').attr('src', this.img_dir + 'spellc.gif'));
944     }
945     return span;
946 };
947
948
949 //////
950 // State functions
951 /////
952 this.flashNoSpellingErrorState = function(on_finish)
953 {
954     this.setStateChanged('no_error_found');
955
956     var ref = this;
957     if (this.main_controller) {
958             var no_spell_errors;
959             if (on_finish) {
960             var fn = function() {
961                 on_finish();
962                     ref.checkSpellingState();
963             };
964             no_spell_errors = fn;
965             }
966             else
967             no_spell_errors = function () { ref.checkSpellingState() };
968
969         var rsm = $('<span>').text(this.lang_no_error_found);
970
971         $(this.switch_lan_pic).hide();
972             $(this.spell_span).empty().append(rsm)
973             .removeClass().addClass('googie_check_spelling_ok');
974
975         window.setTimeout(no_spell_errors, 1000);
976     }
977 };
978
979 this.resumeEditingState = function()
980 {
981     this.setStateChanged('resume_editing');
982
983     //Change link text to resume
984     if (this.main_controller) {
985         var rsm = $('<span>').text(this.lang_rsm_edt);
986         var ref = this;
987
988         $(this.switch_lan_pic).hide();
989         $(this.spell_span).empty().unbind().append(rsm)
990             .bind('click', function() { ref.resumeEditing() })
991             .removeClass().addClass('googie_resume_editing');
992     }
993
994     try { this.edit_layer.scrollTop = this.ta_scroll_top; }
995     catch (e) {};
996 };
997
998 this.checkSpellingState = function(fire)
999 {
1000     if (fire)
1001         this.setStateChanged('ready');
1002
1003     if (this.show_change_lang_pic)
1004         this.switch_lan_pic = this.createChangeLangPic();
1005     else
1006         this.switch_lan_pic = document.createElement('span');
1007
1008     var span_chck = this.createSpellDiv(),
1009         ref = this;
1010
1011     if (this.custom_spellcheck_starter)
1012         $(span_chck).bind('click', function(e) { ref.custom_spellcheck_starter() });
1013     else {
1014         $(span_chck).bind('click', function(e) { ref.spellCheck() });
1015     }
1016
1017     if (this.main_controller) {
1018         if (this.change_lang_pic_placement == 'left') {
1019                 $(this.spell_container).empty().append(this.switch_lan_pic).append(' ').append(span_chck);
1020         } else {
1021                 $(this.spell_container).empty().append(span_chck).append(' ').append(this.switch_lan_pic);
1022             }
1023     }
1024
1025     this.spell_span = span_chck;
1026 };
1027
1028
1029 //////
1030 // Misc. functions
1031 /////
1032 this.isDefined = function(o)
1033 {
1034     return (o !== undefined && o !== null)
1035 };
1036
1037 this.errorFixed = function()
1038 {
1039     this.cnt_errors_fixed++;
1040     if (this.all_errors_fixed_observer)
1041         if (this.cnt_errors_fixed == this.cnt_errors) {
1042             this.hideErrorWindow();
1043             this.all_errors_fixed_observer();
1044         }
1045 };
1046
1047 this.errorFound = function()
1048 {
1049     this.cnt_errors++;
1050 };
1051
1052 this.createCloseButton = function(c_fn)
1053 {
1054     return this.createButton(this.lang_close, 'googie_list_close', c_fn);
1055 };
1056
1057 this.createButton = function(name, css_class, c_fn)
1058 {
1059     var btn_row = document.createElement('tr'),
1060         btn = document.createElement('td'),
1061         spn_btn;
1062
1063     if (css_class) {
1064         spn_btn = document.createElement('span');
1065             $(spn_btn).addClass(css_class).html(name);
1066     } else {
1067         spn_btn = document.createTextNode(name);
1068     }
1069
1070     $(btn).bind('click', c_fn)
1071             .bind('mouseover', this.item_onmouseover)
1072             .bind('mouseout', this.item_onmouseout);
1073
1074     btn.appendChild(spn_btn);
1075     btn_row.appendChild(btn);
1076
1077     return btn_row;
1078 };
1079
1080 this.removeIndicator = function(elm)
1081 {
1082     //$(this.indicator).remove();
1083     // roundcube mod.
1084     if (window.rcmail)
1085         rcmail.set_busy(false, null, this.rc_msg_id);
1086 };
1087
1088 this.appendIndicator = function(elm)
1089 {
1090     // modified by roundcube
1091     if (window.rcmail)
1092             this.rc_msg_id = rcmail.set_busy(true, 'checking');
1093 /*
1094     this.indicator = document.createElement('img');
1095     $(this.indicator).attr('src', this.img_dir + 'indicator.gif')
1096             .css({'margin-right': '5px', 'text-decoration': 'none'}).width(16).height(16);
1097
1098     if (elm)
1099             $(this.indicator).insertBefore(elm);
1100     else
1101         $('body').append(this.indicator);
1102 */
1103 }
1104
1105 this.createFocusLink = function(name)
1106 {
1107     var link = document.createElement('a');
1108     $(link).attr({'href': 'javascript:;', 'name': name});
1109     return link;
1110 };
1111
1112 this.item_onmouseover = function(e)
1113 {
1114     if (this.className != 'googie_list_revert' && this.className != 'googie_list_close')
1115         this.className = 'googie_list_onhover';
1116     else
1117         this.parentNode.className = 'googie_list_onhover';
1118 };
1119
1120 this.item_onmouseout = function(e)
1121 {
1122     if (this.className != 'googie_list_revert' && this.className != 'googie_list_close')
1123         this.className = 'googie_list_onout';
1124     else
1125         this.parentNode.className = 'googie_list_onout';
1126 };
1127
1128
1129 };