1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
8 *
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
13 *
14 * The Initial Developer of the Original Code is Fireinput Inc.
15 *
16 * Portions created by the Initial Developer are Copyright (C) 2007
17 * the Initial Developer. All Rights Reserved.
18 *
19 * Contributor(s):
20 * Olly Ja <ollyja@gmail.com>
21 *
22 * Alternatively, the contents of this file may be used under the terms of
23 * either the GNU General Public License Version 2 or later (the "GPL"), or
24 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
25 * in which case the provisions of the GPL or the LGPL are applicable instead
26 * of those above. If you wish to allow use of your version of this file only
27 * under the terms of either the GPL or the LGPL, and not to allow others to
28 * use your version of this file under the terms of the MPL, indicate your
29 * decision by deleting the provisions above and replace them with the notice
30 * and other provisions required by the GPL or the LGPL. If you do not delete
31 * the provisions above, a recipient may use your version of this file under
32 * the terms of any one of the MPL, the GPL or the LGPL.
33 *
34 * ***** END LICENSE BLOCK *****
35 */
36
37 // Preference handling
38 const CC = Components.classes;
39 const CR = Components.results;
40
41 const PrefService = CC["@mozilla.org/preferences-service;1"];
42 const EnvService = CC["@mozilla.org/process/environment;1"];
43 const UnicodeService = CC["@mozilla.org/intl/scriptableunicodeconverter"];
44
45 const LocaleService = CC["@mozilla.org/intl/nslocaleservice;1"];
46 const StringBundleService = CC["@mozilla.org/intl/stringbundle;1"];
47 const OSService = CC["@mozilla.org/network/protocol;1?name=http"];
48
49 const nsISelectionPrivate = Components.interfaces.nsISelectionPrivate;
50 const nsISelectionController = Components.interfaces.nsISelectionController;
51
52 var gPref = PrefService.getService(Components.interfaces.nsIPrefBranch);
53 var gEnv = EnvService.getService(Components.interfaces.nsIEnvironment);
54 var gUnicode = UnicodeService.getService(Components.interfaces.nsIScriptableUnicodeConverter);
55 var gOS = OSService.getService(Components.interfaces.nsIHttpProtocolHandler);
56
57 var FireinputEnv =
58 {
59 getEnv: function(name)
60 {
61 return gEnv.get(name);
62 },
63
64 getOS: function()
65 {
66 var oscpu = gOS.oscpu;
67 if(/linux/i.test(oscpu))
68 return "Linux";
69 if(/Mac/i.test(oscpu))
70 return "Mac";
71 if(/Windows/i.test(oscpu))
72 return "Windows";
73
74 return "Linux";
75 }
76 };
77
78
79 var FireinputPref =
80 {
81
82 getPref: function(name, type)
83 {
84 var prefName = prefDomain + "." + name;
85
86 if(type == 'undefined')
87 type = gPref.getPrefType(prefName);
88
89 if (type == "INT" || type == gPref.PREF_INT)
90 return gPref.getIntPref(prefName);
91 else if (type == "BOOL" || type == gPref.PREF_BOOL)
92 return gPref.getBoolPref(prefName);
93 else if(type == "STRING" || type == gPref.PREF_STRING)
94 return gPref.getCharPref(prefName);
95 else
96 return null;
97 },
98
99 setPref: function(name, type, value)
100 {
101 var prefName = prefDomain + "." + name;
102
103 if(type == 'undefined')
104 type = gPref.getPrefType(prefName);
105
106 if (type == "INT" || type == gPref.PREF_INT)
107 gPref.setIntPref(prefName, value);
108 else if (type == "BOOL" || type == gPref.PREF_BOOL)
109 gPref.setBoolPref(prefName, value);
110 else
111 gPref.setCharPref(prefName, value);
112 },
113
114 addObserver: function(aTopic, aOwnsWeak)
115 {
116 var pbi = gPref.QueryInterface(Components.interfaces.nsIPrefBranch2);
117 pbi.addObserver(prefDomain, aTopic, aOwnsWeak);
118 }
119
120 };
121
122 var FireinputUnicode =
123 {
124 getUnicodeString: function(text, charset)
125 {
126 gUnicode.charset = charset ? charset : "UTF-8";
127 try
128 {
129 return gUnicode.ConvertToUnicode(text);
130 }
131 catch(ex)
132 {
133 return text;
134 }
135 },
136
137 convertFromUnicode: function(text, charset)
138 {
139 try
140 {
141 gUnicode.charset = charset ? charset : "UTF-8";
142 text = gUnicode.ConvertFromUnicode(text);
143 return text + gUnicode.Finish();
144 }
145 catch(ex)
146 {
147 return text;
148 }
149 }
150 };
151
152
153 var FireinputUtils =
154 {
155 $STR: function(name)
156 {
157 return document.getElementById("strings_fireinput").getString(name);
158 },
159
160 $STRF: function(name, args)
161 {
162 return document.getElementById("strings_fireinput").getFormattedString(name, args);
163 },
164
165 setFocus: function(target)
166 {
167 try {
168 target.focus();
169 }
170 catch(e)
171 {
172 }
173 },
174
175 insertCharAtCaret: function (element, text, sourceType)
176 {
177 if(!element)
178 return;
179
180 // text field. We can only insert bb code here
181 if(element.target.setSelectionRange)
182 {
183 var start = element.target.selectionStart;
184 var end = element.target.selectionEnd;
185 var target = element.target;
186
187 var scrollTop = target.scrollTop;
188 var scrollLeft = target.scrollLeft;
189 var isBottom = (scrollTop + target.clientHeight) == target.scrollHeight;
190 if(typeof(sourceType) != 'undefined' && sourceType == IMAGE_SOURCE_TYPE)
191 text = "[img]" + text + "[/img]";
192
193 target.value = target.value.substr(0, start)
194 + text + target.value.substr(end, target.value.length);
195
196 if(start != null)
197 {
198 this.setCaretTo(target, start + text.length);
199 }
200 else
201 {
202 target.value += text;
203 this.setCaretTo(target, target.value.length);
204 }
205
206 if(typeof(scrollLeft) != "undefined")
207 target.scrollLeft = scrollLeft;
208 if(typeof(scrollTop) != "undefined")
209 {
210 if(!isBottom)
211 target.scrollTop = scrollTop;
212 else
213 target.scrollTop = target.scrollHeight;
214 }
215
216
217 }
218 else if(element.documentTarget)
219 {
220 var win = element.target;
221 var doc = win.content.document;
222 var selection = win.getSelection();
223
224 if(!selection.focusNode)
225 return;
226
227 //FireinputLog.debug(this, "selection.focusNode.nodeType: " + selection.focusNode.nodeType);
228 //FireinputLog.debug(this, "selection.focusNode.name: " + selection.focusNode.nodeName);
229 //FireinputLog.debug(this, "selection.focusNode.value: " + selection.focusNode.nodeValue);
230 //FireinputLog.debug(this, "selection.focusOffset: " + selection.focusOffset);
231
232 //FireinputLog.debug(this, "selection.anchorNode.nodeType: " + selection.anchorNode.nodeType);
233 //FireinputLog.debug(this, "selection.anchorNode.nodeName: " + selection.anchorNode.nodeName);
234 //FireinputLog.debug(this, "selection.anchorOffset: " + selection.anchorOffset);
235 //FireinputLog.debug(this, "selection.isCollapsed: " + selection.isCollapsed);
236 //FireinputLog.debug(this, "selection.rangeCount: " + selection.rangeCount);
237
238 if(!selection.isCollapsed)
239 selection.collapseToEnd();
240
241 if (selection.focusNode.nodeType == Node.TEXT_NODE)
242 {
243 var endOffset = selection.focusOffset;
244 var nodeValue = selection.focusNode.nodeValue;
245
246 if(typeof(sourceType) != 'undefined' && sourceType == IMAGE_SOURCE_TYPE)
247 {
248 var range = selection.getRangeAt(0);
249 var imgNode = doc.createElement("img");
250 imgNode.setAttribute("src", text);
251
252 var startContainer = range.startContainer;
253 var startOffset = range.startOffset;
254 var beforeNode = startContainer.splitText(startOffset);
255 var parentNode = beforeNode.parentNode;
256 parentNode.insertBefore(imgNode, beforeNode);
257 selection.collapse(beforeNode, 0);
258 }
259 else
260 {
261 nodeValue = nodeValue.substr(0,endOffset) + text +
262 nodeValue.substr(endOffset, nodeValue.length);
263 selection.focusNode.nodeValue = nodeValue;
264 selection.collapse(selection.focusNode, endOffset+text.length);
265 }
266
267 }
268 else
269 {
270 this.insertAtSelection(selection, text, sourceType);
271 }
272 }
273 else
274 {
275 element.target.value += text;
276 }
277 },
278
279 setCaretTo: function (obj, pos)
280 {
281 if(obj.selectionStart)
282 {
283 // should turn on focus ?
284 // obj.focus();
285 obj.setSelectionRange(pos, pos);
286 }
287 },
288
289 getCaretPos: function(obj)
290 {
291 return obj.selectionEnd;
292 },
293
294 insertAtSelection: function(selection, text, sourceType)
295 {
296 var range = selection.getRangeAt(0);
297 var ancestorContainer = range.commonAncestorContainer;
298 var doc = ancestorContainer.ownerDocument;
299
300 var startContainer = range.startContainer;
301 var startOffset = range.startOffset;
302
303 try {
304 if (ancestorContainer == doc.body)
305 ancestorContainer = doc.documentElement;
306 } catch (e) { }
307
308
309 // each path is a "child sequence" (a.k.a. "tumbler") that
310 // descends from the ancestor down to the boundary point
311 var startPath = this.getPath(ancestorContainer, startContainer);
312
313 startContainer = ancestorContainer;
314
315 var canInsert = ancestorContainer.hasChildNodes();
316 if (canInsert)
317 {
318 var i;
319 for (i = startPath ? startPath.length-1 : -1; i >= 0; i--)
320 {
321 startContainer = startContainer.childNodes.item(startPath[i]);
322 }
323
324 var tmpNode = "";
325 if(typeof(sourceType) != 'undefined' && sourceType == IMAGE_SOURCE_TYPE)
326 {
327 tmpNode = doc.createElement("img");
328 tmpNode.setAttribute("src", text);
329 }
330 else
331 tmpNode = doc.createTextNode(text);
332
333 startContainer.insertBefore(tmpNode, startContainer.childNodes.item(startOffset));
334 selection.collapse(startContainer, startOffset+1);
335 }
336 },
337
338 getPath: function (ancestor, node)
339 {
340 var n = node;
341 var p = n.parentNode;
342 if (n == ancestor || !p)
343 return null;
344
345 var path = new Array();
346 if (!path)
347 return null;
348 do
349 {
350 for (var i = 0; i < p.childNodes.length; i++)
351 {
352 if (p.childNodes.item(i) == n)
353 {
354 path[path.length] = i;
355 break;
356 }
357 }
358 n = p;
359 p = n.parentNode;
360 } while (n != ancestor && p);
361
362 return path;
363 },
364
365 findPosX: function(obj)
366 {
367 var curleft = 0;
368 if(obj.offsetParent)
369 {
370 while(1)
371 {
372 curleft += obj.offsetLeft;
373 if(!obj.offsetParent)
374 break;
375 obj = obj.offsetParent;
376 }
377 }
378 else if(obj.x)
379 curleft += obj.x;
380 return curleft;
381 },
382
383 findPosY: function(obj)
384 {
385 var curtop = 0;
386 if(obj.offsetParent)
387 {
388 while(1)
389 {
390 curtop += obj.offsetTop;
391 if(!obj.offsetParent)
392 break;
393 obj = obj.offsetParent;
394 }
395 }
396 else if(obj.y)
397 curtop += obj.y;
398 return curtop;
399 },
400
401 getDocumentScrollTop: function(window)
402 {
403 if(!window)
404 return 0;
405 return window.top.document.documentElement.scrollTop ||
406 (window.top.document.body ? window.top.document.body.scrollTop : 0);
407 },
408
409 getDocumentScrollLeft: function(window)
410 {
411 if(!window)
412 return 0;
413
414 return window.top.document.documentElement.scrollLeft ||
415 (window.top.document.body ? window.top.document.body.scrollLeft : 0);
416 },
417
418 getExtensionPath : function()
419 {
420 try
421 {
422 var chromeRegistry = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
423 .getService(Components.interfaces.nsIChromeRegistry);
424 var uri = Components.classes["@mozilla.org/network/standard-url;1"]
425 .createInstance(Components.interfaces.nsIURI);
426
427 uri.spec = "chrome://fireinput/content/";
428
429 var path = chromeRegistry.convertChromeURL(uri);
430 if (typeof(path) == "object") path = path.spec
431
432 if (path.length >= 4 && path.substr(0, 4) == "jar:")
433 {
434 path = path.substring(4); // remove "jar:" at the beginning
435 }
436
437 return path;
438 }
439 catch (e)
440 {
441 }
442
443 return undefined;
444 },
445
446 getAppRootPath: function()
447 {
448 var path = this.getExtensionPath();
449
450 path = path.substring(0, path.lastIndexOf("/extensions/fireinput@software"));
451 return path;
452 },
453
454 trimString: function(str)
455 {
456 return str.replace(/^\s+/g, '').replace(/\s+$/g, '');
457 },
458
459 getLocaleString: function (stringKey)
460 {
461 var stringBundle = StringBundleService.getService(Components.interfaces.nsIStringBundleService);
462 var propertyBundle = "chrome://fireinput/locale/fireinput.properties";
463 var bundle = stringBundle.createBundle(propertyBundle);
464 var str = "";
465 try
466 {
467 str = bundle.GetStringFromName(stringKey);
468 }
469 catch (e)
470 {
471 }
472
473 return str;
474 },
475
476 isValidForDirectInput: function(str)
477 {
478 if(!str)
479 return false;
480 // check whether it's email
481 if(str.indexOf(".") > 0 && str.indexOf("@") > 0)
482 return true;
483
484 // check whether it's url
485 var reg = new RegExp("^[A-Za-z]+://[A-Za-z0-9-]+\.[A-Za-z0-9]+");
486 if(str.test(reg))
487 return true;
488
489 return false;
490 }
491 };
492
493 var FireinputHash = function()
494 {
495 this.items = new Array();
496 };
497
498 FireinputHash.prototype =
499 {
500 foreach: function(callback)
501 {
502 for (var k in this.items)
503 {
504 if(callback) callback(k, this.items[k]);
505 }
506 },
507
508 removeItem: function(in_key)
509 {
510 var tmp_value;
511 if (typeof(this.items[in_key]) != 'undefined')
512 {
513 this.length--;
514 var tmp_value = this.items[in_key];
515 delete this.items[in_key];
516 }
517
518 return tmp_value;
519 },
520
521 getItem: function(in_key)
522 {
523 if (typeof(this.items[in_key]) == 'undefined')
524 return null;
525 else
526 return this.items[in_key];
527 },
528
529 setItem: function(in_key, in_value)
530 {
531 if (typeof(in_value) != 'undefined')
532 {
533 if (typeof(this.items[in_key]) == 'undefined')
534 {
535 this.length++;
536 }
537
538 this.items[in_key] = in_value;
539 }
540
541 return in_value;
542 },
543
544 hasItem: function(in_key) {
545 return typeof(this.items[in_key]) != 'undefined';
546 }
547 };
548
549 var Ajax = function() {};
550 Ajax.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
551
552 Ajax.prototype =
553 {
554 transport: null,
555
556 setOptions: function(options)
557 {
558 this.options =
559 {
560 method: 'post',
561 asynchronous: true,
562 contextType: 'application/x-www-form-urlencoded',
563 parameters: ''
564 }
565
566 extend(this.options, options || {})
567 },
568
569 responseIsSuccess: function()
570 {
571 return this.transport.status == undefined
572 || this.transport.status == 0
573 || (this.transport.status >= 200 && this.transport.status < 300);
574 },
575
576 responseIsFailure: function()
577 {
578 return !this.responseIsSuccess();
579 },
580
581 request: function(url)
582 {
583 this.transport = new XMLHttpRequest();
584
585 var parameters = this.options.parameters || '';
586 if (parameters.length > 0) parameters += '&_=';
587
588 try
589 {
590 this.url = url;
591 if (this.options.method == 'get' && parameters.length > 0)
592 this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;
593
594 this.transport.open(this.options.method, this.url,
595 this.options.asynchronous);
596 if (this.options.asynchronous)
597 {
598 this.transport.onreadystatechange = bind(this.onStateChange, this);
599 setTimeout(bind(function() {this.respondToReadyState(1)}, this), 10);
600 }
601
602 this.setRequestHeaders();
603
604 var body = this.options.postBody ? this.options.postBody : parameters;
605 this.transport.send(this.options.method == 'post' ? body : null);
606
607 }
608 catch (e)
609 {
610 this.dispatchException(e);
611 }
612 },
613
614 setRequestHeaders: function()
615 {
616 var requestHeaders =
617 ['X-Requested-With', 'XMLHttpRequest',
618 'X-Prototype-Version', '1.0',
619 'Accept', 'text/javascript, text/html, application/xml, text/xml, */*'];
620
621 if (this.options.method == 'post')
622 {
623 requestHeaders.push('Content-type', this.options.contentType);
624
625 if (this.transport.overrideMimeType)
626 requestHeaders.push('Connection', 'close');
627 }
628
629 if (this.options.requestHeaders)
630 requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
631
632 for (var i = 0; i < requestHeaders.length; i += 2)
633 this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
634 },
635
636 onStateChange: function()
637 {
638 var readyState = this.transport.readyState;
639 if (readyState != 1)
640 this.respondToReadyState(this.transport.readyState);
641 },
642
643 header: function(name)
644 {
645 try {
646 return this.transport.getResponseHeader(name);
647 }
648 catch (e) {}
649
650 return null;
651 },
652
653 evalJSON: function()
654 {
655 try {
656 return eval('(' + this.header('X-JSON') + ')');
657 } catch (e) {}
658
659 return null;
660 },
661
662 evalResponse: function()
663 {
664 try {
665 return eval(this.transport.responseText);
666 }
667 catch (e)
668 {
669 this.dispatchException(e);
670 }
671
672 return null;
673 },
674
675
676 respondToReadyState: function(readyState)
677 {
678 var event = Ajax.Events[readyState];
679 var transport = this.transport, json = this.evalJSON();
680
681 if (event == 'Complete')
682 {
683 try {
684 (this.options['on' + this.transport.status]
685 || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
686 )(transport, json);
687 }
688 catch (e)
689 {
690 this.dispatchException(e);
691 }
692 if ((this.header('Content-type') || '').match(/^text\/javascript/i))
693 this.evalResponse();
694 }
695
696 try {
697 if(typeof(this.options['on' + event]) != "undefined")
698 (this.options['on' + event])(transport, json);
699 }
700 catch (e) {
701 this.dispatchException(e);
702 }
703
704 },
705 dispatchException: function(exception)
706 {
707 if(this.options.onException)
708 (this.options.onException)(this, exception);
709 }
710 };
711
712
syntax highlighted by Code2HTML, v. 0.9.1