OSDN Git Service

import source-tree based svn r84.
[bluegriffon/BlueGriffon.git] / base / content / bluegriffon / utils / editorUtils.js
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 Original Code is BlueGriffon.
15  *
16  * The Initial Developer of the Original Code is
17  * Disruptive Innovations SARL.
18  * Portions created by the Initial Developer are Copyright (C) 2006
19  * the Initial Developer. All Rights Reserved.
20  *
21  * Contributor(s):
22  *   Daniel Glazman <daniel.glazman@disruptive-innovations.com>, Original author
23  *
24  * Alternatively, the contents of this file may be used under the terms of
25  * either the GNU General Public License Version 2 or later (the "GPL"), or
26  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27  * in which case the provisions of the GPL or the LGPL are applicable instead
28  * of those above. If you wish to allow use of your version of this file only
29  * under the terms of either the GPL or the LGPL, and not to allow others to
30  * use your version of this file under the terms of the MPL, indicate your
31  * decision by deleting the provisions above and replace them with the notice
32  * and other provisions required by the GPL or the LGPL. If you do not delete
33  * the provisions above, a recipient may use your version of this file under
34  * the terms of any one of the MPL, the GPL or the LGPL.
35  *
36  * ***** END LICENSE BLOCK ***** */
37
38 var EditorUtils = {
39
40   /********** PUBLIC **********/
41
42   getCurrentTabEditor: function getCurrentTabEditor()
43   {
44     var tmpWindow = window;
45
46 #ifdef XP_MACOSX
47     // do we have an app-modal window on MAC OS X ?
48     if (window.location.href != "chrome://bluegriffon/content/xul/bluegriffon.xul" &&
49         !tmpWindow.opener)
50     {
51             var windowManager = Components.classes[kWINDOWMEDIATOR_CID].getService();
52             var windowManagerInterface = windowManager.QueryInterface(nsIWindowMediator);
53       tmpWindow = windowManagerInterface.getMostRecentWindow("bluegriffon");
54     }
55 #endif
56     
57     do {
58       var tabeditor = tmpWindow.document.getElementById("tabeditor");
59   
60       if (tabeditor)
61         return tabeditor ;
62   
63       tmpWindow = tmpWindow.opener;
64     } 
65     while (tmpWindow);
66   
67     return null;
68   },
69   
70   getCurrentEditorElement: function getCurrentEditorElement()
71   {
72         var tabeditor = this.getCurrentTabEditor();
73     if (tabeditor)
74       return tabeditor.getCurrentEditorElement() ;
75     return null;
76   },
77   
78   getCurrentEditor: function getCurrentEditor()
79   {
80     // Get the active editor from the <editor> tag
81     var editor = null;
82     try {
83       var editorElement = this.getCurrentEditorElement();
84       if (editorElement)
85       {
86         editor = editorElement.getEditor(editorElement.contentWindow);
87     
88         // Do QIs now so editor users won't have to figure out which interface to use
89         // Using "instanceof" does the QI for us.
90         editor instanceof Components.interfaces.nsIEditor;
91         editor instanceof Components.interfaces.nsIPlaintextEditor;
92         editor instanceof Components.interfaces.nsIHTMLEditor;
93       }
94     } catch (e) { dump("Error in GetCurrentEditor: " + e + "\n"); }
95   
96     return editor;
97   },
98
99   getCurrentDocument: function getCurrentDocument()
100   {
101     // Get the active editor from the <editor> tag
102     var editor = this.getCurrentEditor();
103     if (editor)
104       return editor.document;
105     return null;
106   },
107   
108   getCurrentCommandManager: function getCurrentCommandManager()
109   {
110     try {
111       return this.getCurrentEditorElement().commandManager;
112     } catch (e) { dump (e)+"\n"; }
113
114     return null;
115   },
116   
117   newCommandParams: function newCommandParams()
118   {
119     try {
120       const contractId = "@mozilla.org/embedcomp/command-params;1";
121       const nsICommandParams = Components.interfaces.nsICommandParams;
122
123       return Components.classes[contractId].createInstance(nsICommandParams);
124     }
125     catch(e) { dump("error thrown in newCommandParams: "+e+"\n"); }
126     return null;
127   },
128
129   getCurrentEditingSession: function getCurrentEditingSession()
130   {
131     try {
132       return this.getCurrentEditorElement().editingSession;
133     } catch (e) { dump (e)+"\n"; }
134
135     return null;
136   },
137
138   getCurrentEditorType: function getCurrentEditorType()
139   {
140     try {
141       return this.getCurrentEditorElement().editortype;
142     } catch (e) { dump (e)+"\n"; }
143
144     return "";
145   },
146
147   isAlreadyEdited: function isAlreadyEdited(aURL)
148   {
149     // blank documents are never "already edited"...
150     if (UrlUtils.isUrlAboutBlank(aURL))
151       return null;
152   
153     var url = UrlUtils.newURI(aURL).spec;
154   
155     var windowManager = Components.classes[kWINDOWMEDIATOR_CID].getService();
156     var windowManagerInterface = windowManager.QueryInterface(nsIWindowMediator);
157     var enumerator = windowManagerInterface.getEnumerator( "bluegriffon" );
158     while ( enumerator.hasMoreElements() )
159     {
160       var win = enumerator.getNext().QueryInterface(nsIDOMWindowInternal);
161       try {
162         var mixed = win.gDialog.tabeditor.isAlreadyEdited(url);
163         if (mixed)
164           return {window: win, editor: mixed.editor, index: mixed.index};
165       }
166       catch(e) {}
167     }
168     return null;
169   },
170
171   isDocumentEditable: function isDocumentEditable()
172   {
173     try {
174       return this.getCurrentEditor().isDocumentEditable;
175     } catch (e) { dump (e)+"\n"; }
176     return false;
177   },
178
179   isDocumentModified: function isDocumentModified()
180   {
181     try {
182       return this.getCurrentEditor().documentModified;
183     } catch (e) { dump (e)+"\n"; }
184     return false;
185   },
186
187   isDocumentEmpty: function isDocumentEmpty()
188   {
189     try {
190       return this.getCurrentEditor().documentIsEmpty;
191     } catch (e) { dump (e)+"\n"; }
192     return false;
193   },
194
195   getDocumentTitle: function getDocumentTitle()
196   {
197     try {
198       return this.getCurrentDocument().title;
199     } catch (e) { dump (e)+"\n"; }
200
201     return "";
202   },
203
204   isHTMLEditor: function isHTMLEditor()
205   {
206     // We don't have an editorElement, just return false
207     if (!this.getCurrentEditorElement())
208       return false;
209
210     var editortype = this.getCurrentEditorType();
211     switch (editortype)
212     {
213         case "html":
214         case "htmlmail":
215           return true;
216
217         case "text":
218         case "textmail":
219           return false
220
221         default:
222           dump("INVALID EDITOR TYPE: " + editortype + "\n");
223           break;
224     }
225     return false;
226   },
227
228   isEditingRenderedHTML: function isEditingRenderedHTML()
229   {
230     return this.isHTMLEditor(); // && !this.isInHTMLSourceMode();
231   },
232
233   setDocumentTitle: function setDocumentTitle(title)
234   {
235     try {
236       this.getCurrentEditor().setDocumentTitle(title);
237
238       // Update window title (doesn't work if called from a dialog)
239       if ("UpdateWindowTitle" in window)
240         window.UpdateWindowTitle();
241       else if ("UpdateWindowTitle" in window.opener)
242         window.opener.UpdateWindowTitle();
243     } catch (e) { dump (e)+"\n"; }
244   },
245
246   getSelectionContainer: function getSelectionContainer()
247   {
248     var editor = this.getCurrentEditor();
249     if (!editor) return null;
250
251     try {
252       var selection = editor.selection;
253       if (!selection) return null;
254     }
255     catch (e) { return null; }
256
257     var result = { oneElementSelected:false };
258
259     if (selection.isCollapsed) {
260       result.node = selection.focusNode;
261     }
262     else {
263       var rangeCount = selection.rangeCount;
264       if (rangeCount == 1) {
265         result.node = editor.getSelectedElement("");
266         var range = selection.getRangeAt(0);
267
268         // check for a weird case : when we select a piece of text inside
269         // a text node and apply an inline style to it, the selection starts
270         // at the end of the text node preceding the style and ends after the
271         // last char of the style. Assume the style element is selected for
272         // user's pleasure
273         if (!result.node &&
274             range.startContainer.nodeType == Node.TEXT_NODE &&
275             range.startOffset == range.startContainer.length &&
276             range.endContainer.nodeType == Node.TEXT_NODE &&
277             range.endOffset == range.endContainer.length &&
278             range.endContainer.nextSibling == null &&
279             range.startContainer.nextSibling == range.endContainer.parentNode)
280           result.node = range.endContainer.parentNode;
281
282         if (!result.node) {
283           // let's rely on the common ancestor of the selection
284           result.node = range.commonAncestorContainer;
285         }
286         else {
287           result.oneElementSelected = true;
288         }
289       }
290       else {
291         // assume table cells !
292         var i, container = null;
293         for (i = 0; i < rangeCount; i++) {
294           range = selection.getRangeAt(i);
295           if (!container) {
296             container = range.startContainer;
297           }
298           else if (container != range.startContainer) {
299             // all table cells don't belong to same row so let's
300             // select the parent of all rows
301             result.node = container.parentNode;
302             break;
303           }
304           result.node = container;
305         }
306       }
307     }
308
309     // make sure we have an element here
310     while (result.node.nodeType != Node.ELEMENT_NODE)
311       result.node = result.node.parentNode;
312
313     // and make sure the element is not a special editor node like
314     // the <br> we insert in blank lines
315     // and don't select anonymous content !!! (fix for bug 190279)
316     editor instanceof Components.interfaces.nsIHTMLEditor;
317     while (result.node.hasAttribute("_moz_editor_bogus_node") ||
318            editor.isAnonymousElement(result.node))
319       result.node = result.node.parentNode;
320
321     return result;
322   },
323
324   getMetaElement: function getMetaElement(aName)
325   {
326     if (aName)
327     {
328       var name = aName.toLowerCase();
329       try {
330         var metanodes = this.getCurrentDocument()
331                           .getElementsByTagName("meta");
332         for (var i = 0; i < metanodes.length; i++)
333         {
334           var metanode = metanodes.item(i);
335           if (metanode && metanode.getAttribute("name") == name)
336             return metanode;
337         }
338       }
339       catch(e) {}
340     }
341     return null;
342   },
343
344   createMetaElement: function createMetaElement(aName)
345   {
346     var editor = this.getCurrentEditor();
347     try {
348       var metanode = editor.createElementWithDefaults("meta");
349       metanode.setAttribute("name", aName);
350       return metanode;
351     }
352     catch(e) {}
353     return null;
354   },
355
356   insertMetaElement: function insertMetaElement(aElt, aContent, aInsertNew, aPrepend)
357   {
358    if (aElt)
359    {
360      var editor = this.getCurrentEditor();
361      try {
362        if (!aContent)
363        {
364          if (!insertNew)
365            editor.deleteNode(aElt);
366        }
367        else
368        {
369          if (aInsertNew)
370          {
371            aElt.setAttribute("content", aContent);
372            if (aPrepend)
373              this.prependHeadElement(aElt);
374            else
375              this.appendHeadElement(aElt);
376          }
377          else
378          {
379            editor.setAttribute(aElt, "content", aContent);
380          }
381        }
382      }
383      catch(e) {}
384    } 
385   },
386
387   getHeadElement: function getHeadElement()
388   {
389     try {
390       var doc = EditorUtils.getCurrentDocument();
391       var heads = doc.getElementsByTagName("head");
392       return heads.item(0);
393     }
394     catch(e) {}
395
396     return null;
397   },
398
399   prependHeadElement: function prependHeadElement(aElt)
400   {
401     var head = this.getHeadElement();
402     if (head)
403       try {
404         var editor = EditorUtils.getCurrentEditor();
405         editor.insertNode(aElt, head, 0, true);
406       }
407       catch(e) {}
408   },
409
410   appendHeadElement: function appendHeadElement(aElt)
411   {
412     var head = this.getHeadElement();
413     if (head)
414     {
415       var pos = 0;
416       if (head.hasChildNodes())
417         pos = head.childNodes.length;
418       try {
419         var editor = EditorUtils.getCurrentEditor();
420         editor.insertNode(aElt, head, pos, true);
421       }
422       catch(e) {}
423     }
424   },
425
426   getTextProperty: function(property, attribute, value, firstHas, anyHas, allHas)
427   {
428     try {
429       if (!gAtomService) GetAtomService();
430       var propAtom = gAtomService.getAtom(property);
431   
432       this.getCurrentEditor().getInlineProperty(propAtom, attribute, value,
433                                                 firstHas, anyHas, allHas);
434     }
435     catch(e) {}
436   },
437
438   getClasses: function(aElt)
439   {
440     var e = aElt;
441     var display = CssUtils.getComputedStyle(e).getPropertyValue("display");
442     while (e && display == "inline" && e.className == "")
443     {
444       e = e.parentNode;
445       display = CssUtils.getComputedStyle(e).getPropertyValue("display");
446     }
447     return {classes: e.className, node: e};
448   },
449
450   getCurrentTableEditor: function()
451   {
452     var editor = this.getCurrentEditor();
453     return (editor &&
454             (editor instanceof Components.interfaces.nsITableEditor)) ? editor : null;
455   },
456
457   isStrictDTD: function()
458   {
459     var doctype = this.getCurrentEditor().document.doctype;
460     return (doctype.publicId.lastIndexOf("Strict") != -1);
461   },
462   
463   isCSSDisabledAndStrictDTD: function()
464   {
465     var prefs = GetPrefs();
466     var IsCSSPrefChecked = prefs.getBoolPref("editor.use_css");
467     return !IsCSSPrefChecked && this.isStrictDTD();
468   },
469
470   getDocumentUrl: function()
471   {
472     try {
473       var aDOMHTMLDoc = this.getCurrentEditor().document.QueryInterface(Components.interfaces.nsIDOMHTMLDocument);
474       return aDOMHTMLDoc.URL;
475     }
476     catch (e) {}
477     return "";
478   },
479
480   isXHTMLDocument: function()
481   {
482     var doctype = this.getCurrentEditor().document.doctype;
483     return (doctype.publicId == "-//W3C//DTD XHTML 1.0 Transitional//EN" ||
484             doctype.publicId == "-//W3C//DTD XHTML 1.0 Strict//EN");
485   },
486
487   getWrapColumn: function()
488   {
489     try {
490       return this.getCurrentEditor().wrapWidth;
491     } catch (e) {}
492     return 0;
493   },
494
495   setDocumentURI: function(uri)
496   {
497     try {
498       // XXX WE'LL NEED TO GET "CURRENT" CONTENT FRAME ONCE MULTIPLE EDITORS ARE ALLOWED
499       this.getCurrentEditorElement().docShell.setCurrentURI(uri);
500     } catch (e) { dump("SetDocumentURI:\n"+e +"\n"); }
501   }
502 };