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