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