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