home *** CD-ROM | disk | FTP | other *** search
/ Chip 2004 April / CMCD0404.ISO / Software / Complet / thunderbird / chrome / mail.jar / content / editor / editor.js < prev    next >
Encoding:
JavaScript  |  2003-10-14  |  104.1 KB  |  3,465 lines

  1. /* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: NPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Netscape Public License
  6.  * Version 1.1 (the "License"); you may not use this file except in
  7.  * compliance with the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/NPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is mozilla.org code.
  16.  *
  17.  * The Initial Developer of the Original Code is 
  18.  * Netscape Communications Corporation.
  19.  * Portions created by the Initial Developer are Copyright (C) 1998-1999
  20.  * the Initial Developer. All Rights Reserved.
  21.  *
  22.  * Contributor(s):
  23.  *    Sammy Ford (sford@swbell.net)
  24.  *    Dan Haddix (dan6992@hotmail.com)
  25.  *    John Ratke (jratke@owc.net)
  26.  *    Ryan Cassin (rcassin@supernova.org)
  27.  *    Daniel Glazman (glazman@netscape.com)
  28.  *
  29.  * Alternatively, the contents of this file may be used under the terms of
  30.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  31.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  32.  * in which case the provisions of the GPL or the LGPL are applicable instead
  33.  * of those above. If you wish to allow use of your version of this file only
  34.  * under the terms of either the GPL or the LGPL, and not to allow others to
  35.  * use your version of this file under the terms of the NPL, indicate your
  36.  * decision by deleting the provisions above and replace them with the notice
  37.  * and other provisions required by the GPL or the LGPL. If you do not delete
  38.  * the provisions above, a recipient may use your version of this file under
  39.  * the terms of any one of the NPL, the GPL or the LGPL.
  40.  *
  41.  * ***** END LICENSE BLOCK ***** */
  42.  
  43. /* Main Composer window UI control */
  44.  
  45. var gComposerWindowControllerID = 0;
  46. var prefAuthorString = "";
  47.  
  48. const kDisplayModeNormal = 0;
  49. const kDisplayModeAllTags = 1;
  50. const kDisplayModeSource = 2;
  51. const kDisplayModePreview = 3;
  52. const kDisplayModeMenuIDs = ["viewNormalMode", "viewAllTagsMode", "viewSourceMode", "viewPreviewMode"];
  53. const kDisplayModeTabIDS = ["NormalModeButton", "TagModeButton", "SourceModeButton", "PreviewModeButton"];
  54. const kNormalStyleSheet = "chrome://editor/content/EditorContent.css";
  55. const kAllTagsStyleSheet = "chrome://editor/content/EditorAllTags.css";
  56. const kParagraphMarksStyleSheet = "chrome://editor/content/EditorParagraphMarks.css";
  57.  
  58. const kTextMimeType = "text/plain";
  59. const kHTMLMimeType = "text/html";
  60.  
  61. const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
  62.  
  63. var gPreviousNonSourceDisplayMode = 1;
  64. var gEditorDisplayMode = -1;
  65. var gDocWasModified = false;  // Check if clean document, if clean then unload when user "Opens"
  66. var gContentWindow = 0;
  67. var gSourceContentWindow = 0;
  68. var gSourceTextEditor = null;
  69. var gContentWindowDeck;
  70. var gFormatToolbar;
  71. var gFormatToolbarHidden = false;
  72. var gViewFormatToolbar;
  73. var gColorObj = { LastTextColor:"", LastBackgroundColor:"", LastHighlightColor:"",
  74.                   Type:"", SelectedType:"", NoDefault:false, Cancel:false,
  75.                   HighlightColor:"", BackgroundColor:"", PageColor:"",
  76.                   TextColor:"", TableColor:"", CellColor:""
  77.                 };
  78. var gDefaultTextColor = "";
  79. var gDefaultBackgroundColor = "";
  80. var gCSSPrefListener;
  81. var gPrefs;
  82. var gLocalFonts = null;
  83.  
  84. var gLastFocusNode = null;
  85. var gLastFocusNodeWasSelected = false;
  86.  
  87. // These must be kept in synch with the XUL <options> lists
  88. var gFontSizeNames = ["xx-small","x-small","small","medium","large","x-large","xx-large"];
  89.  
  90. const nsIFilePicker = Components.interfaces.nsIFilePicker;
  91.  
  92. const kEditorToolbarPrefs = "editor.toolbars.showbutton.";
  93.  
  94. function ShowHideToolbarSeparators(toolbar) {
  95.   var childNodes = toolbar.childNodes;
  96.   var separator = null;
  97.   var hideSeparator = true;
  98.   for (var i = 0; childNodes[i].localName != "spacer"; i++) {
  99.     if (childNodes[i].localName == "toolbarseparator") {
  100.       if (separator)
  101.         separator.hidden = true;
  102.       separator = childNodes[i];
  103.     } else if (!childNodes[i].hidden) {
  104.       if (separator)
  105.         separator.hidden = hideSeparator;
  106.       separator = null;
  107.       hideSeparator = false;
  108.     }
  109.   }
  110. }
  111.  
  112. function ShowHideToolbarButtons()
  113. {
  114.   var array = GetPrefs().getChildList(kEditorToolbarPrefs, {});
  115.   for (var i in array) {
  116.     var prefName = array[i];
  117.     var id = prefName.substr(kEditorToolbarPrefs.length) + "Button";
  118.     var button = document.getElementById(id);
  119.     if (button)
  120.       button.hidden = !gPrefs.getBoolPref(prefName);
  121.   }
  122.   ShowHideToolbarSeparators(document.getElementById("EditToolbar"));
  123.   ShowHideToolbarSeparators(document.getElementById("FormatToolbar"));
  124. }
  125.   
  126. function AddToolbarPrefListener()
  127. {
  128.   try {
  129.     var pbi = GetPrefs().QueryInterface(Components.interfaces.nsIPrefBranchInternal);
  130.     pbi.addObserver(kEditorToolbarPrefs, gEditorToolbarPrefListener, false);
  131.   } catch(ex) {
  132.     dump("Failed to observe prefs: " + ex + "\n");
  133.   }
  134. }
  135.  
  136. function RemoveToolbarPrefListener()
  137. {
  138.   try {
  139.     var pbi = GetPrefs().QueryInterface(Components.interfaces.nsIPrefBranchInternal);
  140.     pbi.removeObserver(kEditorToolbarPrefs, gEditorToolbarPrefListener);
  141.   } catch(ex) {
  142.     dump("Failed to remove pref observer: " + ex + "\n");
  143.   }
  144. }
  145.  
  146. // Pref listener constants
  147. const gEditorToolbarPrefListener =
  148. {
  149.   observe: function(subject, topic, prefName)
  150.   {
  151.     // verify that we're changing a button pref
  152.     if (topic != "nsPref:changed")
  153.       return;
  154.  
  155.     var id = prefName.substr(kEditorToolbarPrefs.length) + "Button";
  156.     var button = document.getElementById(id);
  157.     if (button) {
  158.       button.hidden = !gPrefs.getBoolPref(prefName);
  159.       ShowHideToolbarSeparators(button.parentNode);
  160.     }
  161.   }
  162. };
  163.  
  164. function nsButtonPrefListener()
  165. {
  166.   try {
  167.     var pbi = pref.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
  168.     pbi.addObserver(this.domain, this, false);
  169.   } catch(ex) {
  170.     dump("Failed to observe prefs: " + ex + "\n");
  171.   }
  172. }
  173.  
  174. // implements nsIObserver
  175. nsButtonPrefListener.prototype =
  176. {
  177.   domain: "editor.use_css",
  178.   observe: function(subject, topic, prefName)
  179.   {
  180.     if (!IsHTMLEditor())
  181.       return;
  182.     // verify that we're changing a button pref
  183.     if (topic != "nsPref:changed") return;
  184.     if (prefName.substr(0, this.domain.length) != this.domain) return;
  185.  
  186.     var cmd = document.getElementById("cmd_highlight");
  187.     if (cmd) {
  188.       var prefs = GetPrefs();
  189.       var useCSS = prefs.getBoolPref(prefName);
  190.       var editor = GetCurrentEditor();
  191.       if (useCSS && editor) {
  192.         var mixedObj = {};
  193.         var state = editor.getHighlightColorState(mixedObj);
  194.         cmd.setAttribute("state", state);
  195.         cmd.collapsed = false;
  196.       }      
  197.       else {
  198.         cmd.setAttribute("state", "transparent");
  199.         cmd.collapsed = true;
  200.       }
  201.  
  202.       if (editor)
  203.         editor.isCSSEnabled = useCSS;
  204.     }
  205.   }
  206. }
  207.  
  208. function AfterHighlightColorChange()
  209. {
  210.   if (!IsHTMLEditor())
  211.     return;
  212.  
  213.   var button = document.getElementById("cmd_highlight");
  214.   if (button) {
  215.     var mixedObj = {};
  216.     try {
  217.       var state = GetCurrentEditor().getHighlightColorState(mixedObj);
  218.       button.setAttribute("state", state);
  219.       onHighlightColorChange();
  220.     } catch (e) {}
  221.   }      
  222. }
  223.  
  224. function EditorOnLoad()
  225. {
  226.     // See if argument was passed.
  227.     if ( window.arguments && window.arguments[0] ) {
  228.         // Opened via window.openDialog with URL as argument.
  229.         // Put argument where EditorStartup expects it.
  230.         document.getElementById( "args" ).setAttribute( "value", window.arguments[0] );
  231.     }
  232.  
  233.     // get default character set if provided
  234.     if ("arguments" in window && window.arguments.length > 1 && window.arguments[1]) {
  235.       if (window.arguments[1].indexOf("charset=") != -1) {
  236.         var arrayArgComponents = window.arguments[1].split("=");
  237.         if (arrayArgComponents) {
  238.           // Put argument where EditorStartup expects it.
  239.           document.getElementById( "args" ).setAttribute("charset", arrayArgComponents[1]);
  240.         }
  241.       }
  242.     }
  243.  
  244.     window.tryToClose = EditorCanClose;
  245.  
  246.     // Continue with normal startup.
  247.     EditorStartup();
  248.  
  249.     // Initialize our source text <editor>
  250.     try {
  251.       gSourceContentWindow = document.getElementById("content-source");
  252.       gSourceContentWindow.makeEditable("text", false);
  253.       gSourceTextEditor = gSourceContentWindow.getEditor(gSourceContentWindow.contentWindow);
  254.       gSourceTextEditor.QueryInterface(Components.interfaces.nsIPlaintextEditor);
  255.       gSourceTextEditor.enableUndo(false);
  256.       gSourceTextEditor.rootElement.style.fontFamily = "-moz-fixed";
  257.       gSourceTextEditor.rootElement.style.whiteSpace = "pre";
  258.       gSourceTextEditor.rootElement.style.margin = 0;
  259.       var controller = Components.classes["@mozilla.org/embedcomp/base-command-controller;1"]
  260.                                  .createInstance(Components.interfaces.nsIControllerContext);
  261.       controller.init(null);
  262.       controller.setCommandContext(gSourceContentWindow);
  263.       gSourceContentWindow.contentWindow.controllers.insertControllerAt(0, controller);
  264.       var commandTable = controller.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
  265.                                    .getInterface(Components.interfaces.nsIControllerCommandTable);
  266.       commandTable.registerCommand("cmd_find",        nsFindCommand);
  267.       commandTable.registerCommand("cmd_findNext",    nsFindAgainCommand);
  268.       commandTable.registerCommand("cmd_findPrev",    nsFindAgainCommand);
  269.     } catch (e) { dump("makeEditable failed: "+e+"\n"); }
  270. }
  271.  
  272. const gSourceTextListener =
  273. {
  274.   NotifyDocumentCreated: function NotifyDocumentCreated() {},
  275.   NotifyDocumentWillBeDestroyed: function NotifyDocumentWillBeDestroyed() {},
  276.   NotifyDocumentStateChanged: function NotifyDocumentStateChanged(isChanged)
  277.   {
  278.     window.updateCommands("save");
  279.   }
  280. };
  281.  
  282. const gSourceTextObserver =
  283. {
  284.   observe: function observe(aSubject, aTopic, aData)
  285.   {
  286.     // we currently only use this to update undo
  287.     window.updateCommands("undo");
  288.   }
  289. };
  290.  
  291. function TextEditorOnLoad()
  292. {
  293.     // See if argument was passed.
  294.     if ( window.arguments && window.arguments[0] ) {
  295.         // Opened via window.openDialog with URL as argument.
  296.         // Put argument where EditorStartup expects it.
  297.         document.getElementById( "args" ).setAttribute( "value", window.arguments[0] );
  298.     }
  299.     // Continue with normal startup.
  300.     EditorStartup();
  301. }
  302.  
  303. // This should be called by all editor users when they close their window
  304. //  or other similar "done with editor" actions, like recycling a Mail Composer window.
  305. function EditorCleanup()
  306. {
  307.   SwitchInsertCharToAnotherEditorOrClose();
  308. }
  309.  
  310. var DocumentReloadListener =
  311. {
  312.   NotifyDocumentCreated: function() {},
  313.   NotifyDocumentWillBeDestroyed: function() {},
  314.  
  315.   NotifyDocumentStateChanged:function( isNowDirty )
  316.   {
  317.     var editor = GetCurrentEditor();
  318.     try {
  319.       // unregister the listener to prevent multiple callbacks
  320.       editor.removeDocumentStateListener( DocumentReloadListener );
  321.  
  322.       var charset = editor.documentCharacterSet;
  323.  
  324.       // update the META charset with the current presentation charset
  325.       editor.documentCharacterSet = charset;
  326.  
  327.     } catch (e) {}
  328.   }
  329. };
  330.  
  331. function addEditorClickEventListener()
  332. {
  333.   try {
  334.     var bodyelement = GetBodyElement();
  335.     if (bodyelement)
  336.       bodyelement.addEventListener("click", EditorClick, false);
  337.   } catch (e) {}
  338. }
  339.  
  340. // implements nsIObserver
  341. var gEditorDocumentObserver =
  342.   observe: function(aSubject, aTopic, aData)
  343.   {
  344.     // Should we allow this even if NOT the focused editor?
  345.     var commandManager = GetCurrentCommandManager();
  346.     if (commandManager != aSubject)
  347.       return;
  348.  
  349.     var editor = GetCurrentEditor();
  350.     switch(aTopic)
  351.     {
  352.       case "obs_documentCreated":
  353.         // Just for convenience
  354.         gContentWindow = window.content;
  355.  
  356.         // Get state to see if document creation succeeded
  357.         var params = newCommandParams();
  358.         if (!params)
  359.           return;
  360.  
  361.         try {
  362.           commandManager.getCommandState(aTopic, gContentWindow, params);
  363.           var errorStringId = 0;
  364.           var editorStatus = params.getLongValue("state_data");
  365.           if (!editor && editorStatus == nsIEditingSession.eEditorOK)
  366.           {
  367.             dump("\n ****** NO EDITOR BUT NO EDITOR ERROR REPORTED ******* \n\n");
  368.             editorStatus = nsIEditingSession.eEditorErrorUnkown;
  369.           }
  370.  
  371.           switch (editorStatus)
  372.           {
  373.             case nsIEditingSession.eEditorErrorCantEditFramesets:
  374.               errorStringId = "CantEditFramesetMsg";
  375.               break;
  376.             case nsIEditingSession.eEditorErrorCantEditMimeType:
  377.               errorStringId = "CantEditMimeTypeMsg";
  378.               break;
  379.             case nsIEditingSession.eEditorErrorUnkown:
  380.               errorStringId = "CantEditDocumentMsg";
  381.               break;
  382.             // Note that for "eEditorErrorFileNotFound, 
  383.             // network code popped up an alert dialog, so we don't need to
  384.           }
  385.           if (errorStringId)
  386.             AlertWithTitle("", GetString(errorStringId));
  387.         } catch(e) { dump("EXCEPTION GETTING obs_documentCreated state "+e+"\n"); }
  388.  
  389.         // We have a bad editor -- nsIEditingSession will rebuild an editor
  390.         //   with a blank page, so simply abort here
  391.         if (editorStatus)
  392.           return; 
  393.  
  394.         if (!("InsertCharWindow" in window))
  395.           window.InsertCharWindow = null;
  396.  
  397.         try {
  398.           editor.QueryInterface(nsIEditorStyleSheets);
  399.  
  400.           //  and extra styles for showing anchors, table borders, smileys, etc
  401.           editor.addOverrideStyleSheet(kNormalStyleSheet);
  402.         } catch (e) {}
  403.  
  404.         // Things for just the Web Composer application
  405.         if (IsWebComposer())
  406.         {
  407.           // Set focus to content window if not a mail composer
  408.           // Race conditions prevent us from setting focus here
  409.           //   when loading a url into blank window
  410.           setTimeout(SetFocusOnStartup, 0);
  411.  
  412.           // Call EditorSetDefaultPrefsAndDoctype first so it gets the default author before initing toolbars
  413.           EditorSetDefaultPrefsAndDoctype();
  414.  
  415.           // We may load a text document into an html editor,
  416.           //   so be sure editortype is set correctly
  417.           // XXX We really should use the "real" plaintext editor for this!
  418.           if (editor.contentsMIMEType == "text/plain")
  419.           {
  420.             try {
  421.               GetCurrentEditorElement().editortype = "text";
  422.             } catch (e) { dump (e)+"\n"; }
  423.  
  424.             // Hide or disable UI not used for plaintext editing
  425.             HideItem("FormatToolbar");
  426.             HideItem("EditModeToolbar");
  427.             HideItem("formatMenu");
  428.             HideItem("tableMenu");
  429.             HideItem("menu_validate");
  430.             HideItem("sep_validate");
  431.             HideItem("previewButton");
  432.             HideItem("imageButton");
  433.             HideItem("linkButton");
  434.             HideItem("namedAnchorButton");
  435.             HideItem("hlineButton");
  436.             HideItem("tableButton");
  437.  
  438.             HideItem("fileExportToText");
  439.             HideItem("previewInBrowser");
  440.  
  441. /* XXX When paste actually converts formatted rich text to pretty formatted plain text
  442.        and pasteNoFormatting is fixed to paste the text without formatting (what paste
  443.        currently does), then this item shouldn't be hidden: */
  444.             HideItem("menu_pasteNoFormatting"); 
  445.  
  446.             HideItem("cmd_viewFormatToolbar");
  447.             HideItem("cmd_viewEditModeToolbar");
  448.  
  449.             HideItem("viewSep1");
  450.             HideItem("viewNormalMode");
  451.             HideItem("viewAllTagsMode");
  452.             HideItem("viewSourceMode");
  453.             HideItem("viewPreviewMode");
  454.  
  455.             HideItem("structSpacer");
  456.  
  457.             // Hide everything in "Insert" except for "Symbols"
  458.             var menuPopup = document.getElementById("insertMenuPopup");
  459.             if (menuPopup)
  460.             {
  461.               var children = menuPopup.childNodes;
  462.               for (var i=0; i < children.length; i++) 
  463.               {
  464.                 var item = children.item(i);
  465.                 if (item.id != "insertChars")
  466.                   item.hidden = true;
  467.               }
  468.             }
  469.           }
  470.     
  471.           // Set window title
  472.           UpdateWindowTitle();
  473.  
  474.           // We must wait until document is created to get proper Url
  475.           // (Windows may load with local file paths)
  476.           SetSaveAndPublishUI(GetDocumentUrl());
  477.  
  478.           // Start in "Normal" edit mode
  479.           SetDisplayMode(kDisplayModeNormal);
  480.         }
  481.  
  482.         // Add mouse click watcher if right type of editor
  483.         if (IsHTMLEditor())
  484.         {
  485.           addEditorClickEventListener();
  486.  
  487.           // Force color widgets to update
  488.           onFontColorChange();
  489.           onBackgroundColorChange();
  490.         }
  491.         break;
  492.  
  493.       case "cmd_setDocumentModified":
  494.         window.updateCommands("save");
  495.         break;
  496.  
  497.       case "obs_documentWillBeDestroyed":
  498.         dump("obs_documentWillBeDestroyed notification\n");
  499.         break;
  500.  
  501.       case "obs_documentLocationChanged":
  502.         // Ignore this when editor doesn't exist,
  503.         //   which happens once when page load starts
  504.         if (editor)
  505.           try {
  506.             editor.updateBaseURL();
  507.           } catch(e) { dump (e); }
  508.         break;
  509.  
  510.       case "cmd_bold":
  511.         // Update all style items
  512.         // cmd_bold is a proxy; see EditorSharedStartup (above) for details
  513.         window.updateCommands("style");
  514.         window.updateCommands("undo");
  515.         break;
  516.     }
  517.   }
  518. }
  519.  
  520. function SetFocusOnStartup()
  521. {
  522.   gContentWindow.focus();
  523. }
  524.  
  525. function EditorStartup()
  526. {
  527.   var is_HTMLEditor = IsHTMLEditor();
  528.   if (is_HTMLEditor)
  529.   {
  530.     // XUL elements we use when switching from normal editor to edit source
  531.     gContentWindowDeck = document.getElementById("ContentWindowDeck");
  532.     gFormatToolbar = document.getElementById("FormatToolbar");
  533.     gViewFormatToolbar = document.getElementById("viewFormatToolbar");
  534.   }
  535.  
  536.   // set up our global prefs object
  537.   GetPrefsService();
  538.  
  539.   // Startup also used by other editor users, such as Message Composer
  540.   EditorSharedStartup();
  541.  
  542.   // Commands specific to the Composer Application window,
  543.   //  (i.e., not embedded editors)
  544.   //  such as file-related commands, HTML Source editing, Edit Modes...
  545.   SetupComposerWindowCommands();
  546.  
  547.   ShowHideToolbarButtons();
  548.   AddToolbarPrefListener();
  549.  
  550.   gCSSPrefListener = new nsButtonPrefListener();
  551.  
  552.   // hide Highlight button if we are in an HTML editor with CSS mode off
  553.   var cmd = document.getElementById("cmd_highlight");
  554.   if (cmd) {
  555.     var prefs = GetPrefs();
  556.     var useCSS = prefs.getBoolPref("editor.use_css");
  557.     if (!useCSS && is_HTMLEditor) {
  558.       cmd.collapsed = true;
  559.     }
  560.   }
  561.  
  562.   // Get url for editor content and load it.
  563.   // the editor gets instantiated by the edittingSession when the URL has finished loading.
  564.   var url = document.getElementById("args").getAttribute("value");
  565.   try {
  566.     var charset = document.getElementById("args").getAttribute("charset");
  567.     var contentViewer = GetCurrentEditorElement().docShell.contentViewer;
  568.     contentViewer.QueryInterface(Components.interfaces.nsIMarkupDocumentViewer);
  569.     contentViewer.defaultCharacterSet = charset;
  570.     contentViewer.forceCharacterSet = charset;
  571.   } catch (e) {}
  572.   EditorLoadUrl(url);
  573. }
  574.  
  575. function EditorLoadUrl(url)
  576. {
  577.   try {
  578.     if (url)
  579.       GetCurrentEditorElement().webNavigation.loadURI(url, // uri string
  580.              nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE,     // load flags
  581.              null,                                         // referrer
  582.              null,                                         // post-data stream
  583.              null);
  584.   } catch (e) { dump(" EditorLoadUrl failed: "+e+"\n"); }
  585. }
  586.  
  587. // This should be called by all Composer types
  588. function EditorSharedStartup()
  589. {
  590.   // Just for convenience
  591.   gContentWindow = window.content;
  592.  
  593.   // Set up the mime type and register the commands.
  594.   if (IsHTMLEditor())
  595.     SetupHTMLEditorCommands();
  596.   else
  597.     SetupTextEditorCommands();
  598.  
  599.   // add observer to be called when document is really done loading 
  600.   // and is modified
  601.   // Note: We're really screwed if we fail to install this observer!
  602.   try {
  603.     var commandManager = GetCurrentCommandManager();
  604.     commandManager.addCommandObserver(gEditorDocumentObserver, "obs_documentCreated");
  605.     commandManager.addCommandObserver(gEditorDocumentObserver, "cmd_setDocumentModified");
  606.     commandManager.addCommandObserver(gEditorDocumentObserver, "obs_documentWillBeDestroyed");
  607.     commandManager.addCommandObserver(gEditorDocumentObserver, "obs_documentLocationChanged");
  608.  
  609.     // Until nsIControllerCommandGroup-based code is implemented,
  610.     //  we will observe just the bold command to trigger update of
  611.     //  all toolbar style items
  612.     commandManager.addCommandObserver(gEditorDocumentObserver, "cmd_bold");
  613.   } catch (e) { dump(e); }
  614.  
  615.   var isMac = (GetOS() == gMac);
  616.  
  617.   // Set platform-specific hints for how to select cells
  618.   // Mac uses "Cmd", all others use "Ctrl"
  619.   var tableKey = GetString(isMac ? "XulKeyMac" : "TableSelectKey");
  620.   var dragStr = tableKey+GetString("Drag");
  621.   var clickStr = tableKey+GetString("Click");
  622.  
  623.   var delStr = GetString(isMac ? "Clear" : "Del");
  624.  
  625.   SafeSetAttribute("menu_SelectCell", "acceltext", clickStr);
  626.   SafeSetAttribute("menu_SelectRow", "acceltext", dragStr);
  627.   SafeSetAttribute("menu_SelectColumn", "acceltext", dragStr);
  628.   SafeSetAttribute("menu_SelectAllCells", "acceltext", dragStr);
  629.   // And add "Del" or "Clear"
  630.   SafeSetAttribute("menu_DeleteCellContents", "acceltext", delStr);
  631.  
  632.   // Set text for indent, outdent keybinding
  633.  
  634.   // hide UI that we don't have components for
  635.   RemoveInapplicableUIElements();
  636.  
  637.   gPrefs = GetPrefs();
  638.  
  639.   // Use browser colors as initial values for editor's default colors
  640.   var BrowserColors = GetDefaultBrowserColors();
  641.   if (BrowserColors)
  642.   {
  643.     gDefaultTextColor = BrowserColors.TextColor;
  644.     gDefaultBackgroundColor = BrowserColors.BackgroundColor;
  645.   }
  646.  
  647.   // For new window, no default last-picked colors
  648.   gColorObj.LastTextColor = "";
  649.   gColorObj.LastBackgroundColor = "";
  650.   gColorObj.LastHighlightColor = "";
  651. }
  652.  
  653. // This method is only called by Message composer when recycling a compose window
  654. function EditorResetFontAndColorAttributes()
  655. {
  656.   try {  
  657.     document.getElementById("cmd_fontFace").setAttribute("state", "");
  658.     EditorRemoveTextProperty("font", "color");
  659.     EditorRemoveTextProperty("font", "bgcolor");
  660.     EditorRemoveTextProperty("font", "size");
  661.     EditorRemoveTextProperty("small", "");
  662.     EditorRemoveTextProperty("big", "");
  663.     var bodyelement = GetBodyElement();
  664.     if (bodyelement)
  665.     {
  666.       var editor = GetCurrentEditor();
  667.       editor.removeAttributeOrEquivalent(bodyelement, "text", true);
  668.       editor.removeAttributeOrEquivalent(bodyelement, "bgcolor", true);
  669.       bodyelement.removeAttribute("link");
  670.       bodyelement.removeAttribute("alink");
  671.       bodyelement.removeAttribute("vlink");
  672.       editor.removeAttributeOrEquivalent(bodyelement, "background", true);
  673.     }
  674.     gColorObj.LastTextColor = "";
  675.     gColorObj.LastBackgroundColor = "";
  676.     gColorObj.LastHighlightColor = "";
  677.     document.getElementById("cmd_fontColor").setAttribute("state", "");
  678.     document.getElementById("cmd_backgroundColor").setAttribute("state", "");
  679.     UpdateDefaultColors();
  680.   } catch (e) {}
  681. }
  682.  
  683. function EditorShutdown()
  684. {
  685.   RemoveToolbarPrefListener();
  686.  
  687.   try {
  688.     var commandManager = GetCurrentCommandManager();
  689.     commandManager.removeCommandObserver(gEditorDocumentObserver, "obs_documentCreated");
  690.     commandManager.removeCommandObserver(gEditorDocumentObserver, "obs_documentWillBeDestroyed");
  691.     commandManager.removeCommandObserver(gEditorDocumentObserver, "obs_documentLocationChanged");
  692.   } catch (e) { dump (e); }   
  693. }
  694.  
  695. function SafeSetAttribute(nodeID, attributeName, attributeValue)
  696. {
  697.     var theNode = document.getElementById(nodeID);
  698.     if (theNode)
  699.         theNode.setAttribute(attributeName, attributeValue);
  700. }
  701.  
  702. function DocumentHasBeenSaved()
  703. {
  704.   var fileurl = "";
  705.   try {
  706.     fileurl = GetDocumentUrl();
  707.   } catch (e) {
  708.     return false;
  709.   }
  710.  
  711.   if (!fileurl || IsUrlAboutBlank(fileurl))
  712.     return false;
  713.  
  714.   // We have a file URL already
  715.   return true;
  716. }
  717.  
  718. function CheckAndSaveDocument(command, allowDontSave)
  719. {
  720.   var document;
  721.   try {
  722.     // if we don't have an editor or an document, bail
  723.     var editor = GetCurrentEditor();
  724.     document = editor.document;
  725.     if (!document)
  726.       return true;
  727.   } catch (e) { return true; }
  728.  
  729.   if (!IsDocumentModified() && !IsHTMLSourceChanged())
  730.     return true;
  731.  
  732.   // call window.focus, since we need to pop up a dialog
  733.   // and therefore need to be visible (to prevent user confusion)
  734.   top.document.commandDispatcher.focusedWindow.focus();  
  735.  
  736.   var scheme = GetScheme(GetDocumentUrl());
  737.   var doPublish = (scheme && scheme != "file");
  738.  
  739.   var strID;
  740.   switch (command)
  741.   {
  742.     case "cmd_close":
  743.       strID = "BeforeClosing";
  744.       break;
  745.     case "cmd_preview":
  746.       strID = "BeforePreview";
  747.       break;
  748.     case "cmd_editSendPage":
  749.       strID = "SendPageReason";
  750.       break;
  751.     case "cmd_validate":
  752.       strID = "BeforeValidate";
  753.       break;
  754.   }
  755.     
  756.   var reasonToSave = strID ? GetString(strID) : "";
  757.  
  758.   var title = document.title;
  759.   if (!title)
  760.     title = GetString("untitled");
  761.  
  762.   var dialogTitle = GetString(doPublish ? "PublishPage" : "SaveDocument");
  763.   var dialogMsg = GetString(doPublish ? "PublishPrompt" : "SaveFilePrompt");
  764.   dialogMsg = (dialogMsg.replace(/%title%/,title)).replace(/%reason%/,reasonToSave);
  765.  
  766.   var promptService = GetPromptService();
  767.   if (!promptService)
  768.     return false;
  769.  
  770.   var result = {value:0};
  771.   var promptFlags = promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1;
  772.   var button1Title = null;
  773.   var button3Title = null;
  774.  
  775.   if (doPublish)
  776.   {
  777.     promptFlags += promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0;
  778.     button1Title = GetString("Publish");
  779.     button3Title = GetString("DontPublish");    
  780.   }
  781.   else
  782.   {
  783.     promptFlags += promptService.BUTTON_TITLE_SAVE * promptService.BUTTON_POS_0;
  784.   }
  785.  
  786.   // If allowing "Don't..." button, add that
  787.   if (allowDontSave)
  788.     promptFlags += doPublish ?
  789.         (promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_2)
  790.         : (promptService.BUTTON_TITLE_DONT_SAVE * promptService.BUTTON_POS_2);
  791.   
  792.   result = promptService.confirmEx(window, dialogTitle, dialogMsg, promptFlags,
  793.                           button1Title, null, button3Title, null, {value:0});
  794.  
  795.   if (result == 0)
  796.   {
  797.     // Save, but first finish HTML source mode
  798.     if (IsHTMLSourceChanged()) {
  799.       try {
  800.         FinishHTMLSource();
  801.       } catch (e) { return false;}
  802.     }
  803.  
  804.     if (doPublish)
  805.     {
  806.       // We save the command the user wanted to do in a global
  807.       // and return as if user canceled because publishing is asynchronous
  808.       // This command will be fired when publishing finishes
  809.       gCommandAfterPublishing = command;
  810.       goDoCommand("cmd_publish");
  811.       return false;
  812.     }
  813.  
  814.     // Save to local disk
  815.     var contentsMIMEType;
  816.     if (IsHTMLEditor())
  817.       contentsMIMEType = kHTMLMimeType;
  818.     else
  819.       contentsMIMEType = kTextMimeType;
  820.     var success = SaveDocument(false, false, contentsMIMEType);
  821.     return success;
  822.   }
  823.  
  824.   if (result == 2) // "Don't Save"
  825.     return true;
  826.  
  827.   // Default or result == 1 (Cancel)
  828.   return false;
  829. }
  830.  
  831. // --------------------------- File menu ---------------------------
  832.  
  833.  
  834. // used by openLocation. see openLocation.js for additional notes.
  835. function delayedOpenWindow(chrome, flags, url)
  836. {
  837.   dump("setting timeout\n");
  838.   setTimeout("window.openDialog('"+chrome+"','_blank','"+flags+"','"+url+"')", 10);
  839. }
  840.  
  841. function EditorNewPlaintext()
  842. {
  843.   window.openDialog( "chrome://editor/content/TextEditorAppShell.xul",
  844.                      "_blank",
  845.                      "chrome,dialog=no,all",
  846.                      "about:blank");
  847. }
  848.  
  849. // Check for changes to document and allow saving before closing
  850. // This is hooked up to the OS's window close widget (e.g., "X" for Windows)
  851. function EditorCanClose()
  852. {
  853.   // Returns FALSE only if user cancels save action
  854.  
  855.   // "true" means allow "Don't Save" button
  856.   var canClose = CheckAndSaveDocument("cmd_close", true);
  857.  
  858.   // This is our only hook into closing via the "X" in the caption
  859.   //   or "Quit" (or other paths?)
  860.   //   so we must shift association to another
  861.   //   editor or close any non-modal windows now
  862.   if (canClose && "InsertCharWindow" in window && window.InsertCharWindow)
  863.     SwitchInsertCharToAnotherEditorOrClose();
  864.  
  865.   return canClose;
  866. }
  867.  
  868. // --------------------------- View menu ---------------------------
  869.  
  870. function EditorSetDocumentCharacterSet(aCharset)
  871. {
  872.   try {
  873.     var editor = GetCurrentEditor();
  874.     editor.documentCharacterSet = aCharset;
  875.     var docUrl = GetDocumentUrl();
  876.     if( !IsUrlAboutBlank(docUrl))
  877.     {
  878.       // reloading the document will reverse any changes to the META charset, 
  879.       // we need to put them back in, which is achieved by a dedicated listener
  880.       editor.addDocumentStateListener( DocumentReloadListener );
  881.       EditorLoadUrl(docUrl);
  882.     }
  883.   } catch (e) {}
  884. }
  885.  
  886. // ------------------------------------------------------------------
  887. function updateCharsetPopupMenu(menuPopup)
  888. {
  889.   if (IsDocumentModified() && !IsDocumentEmpty())
  890.   {
  891.     for (var i = 0; i < menuPopup.childNodes.length; i++)
  892.     {
  893.       var menuItem = menuPopup.childNodes[i];
  894.       menuItem.setAttribute('disabled', 'true');
  895.     }
  896.   }
  897. }
  898.  
  899. // --------------------------- Text style ---------------------------
  900.  
  901. function onParagraphFormatChange(paraMenuList, commandID)
  902. {
  903.   if (!paraMenuList)
  904.     return;
  905.  
  906.   var commandNode = document.getElementById(commandID);
  907.   var state = commandNode.getAttribute("state");
  908.  
  909.   // force match with "normal"
  910.   if (state == "body")
  911.     state = "";
  912.  
  913.   if (state == "mixed")
  914.   {
  915.     //Selection is the "mixed" ( > 1 style) state
  916.     paraMenuList.selectedItem = null;
  917.     paraMenuList.setAttribute("label",GetString('Mixed'));
  918.   }
  919.   else
  920.   {
  921.     var menuPopup = document.getElementById("ParagraphPopup");
  922.     var menuItems = menuPopup.childNodes;
  923.     for (var i=0; i < menuItems.length; i++)
  924.     {
  925.       var menuItem = menuItems.item(i);
  926.       if ("value" in menuItem && menuItem.value == state)
  927.       {
  928.         paraMenuList.selectedItem = menuItem;
  929.         break;
  930.       }
  931.     }
  932.   }
  933. }
  934.  
  935. function onFontFaceChange(fontFaceMenuList, commandID)
  936. {
  937.   var commandNode = document.getElementById(commandID);
  938.   var state = commandNode.getAttribute("state");
  939.  
  940.   if (state == "mixed")
  941.   {
  942.     //Selection is the "mixed" ( > 1 style) state
  943.     fontFaceMenuList.selectedItem = null;
  944.     fontFaceMenuList.setAttribute("label",GetString('Mixed'));
  945.   }
  946.   else
  947.   {
  948.     var menuPopup = document.getElementById("FontFacePopup");
  949.     var menuItems = menuPopup.childNodes;
  950.     for (var i=0; i < menuItems.length; i++)
  951.     {
  952.       var menuItem = menuItems.item(i);
  953.       if (menuItem.getAttribute("label") && ("value" in menuItem && menuItem.value.toLowerCase() == state.toLowerCase()))
  954.       {
  955.         fontFaceMenuList.selectedItem = menuItem;
  956.         break;
  957.       }
  958.     }
  959.   }
  960. }
  961.  
  962. function EditorSelectFontSize()
  963. {
  964.   var select = document.getElementById("FontSizeSelect");
  965.   if (select)
  966.   {
  967.     if (select.selectedIndex == -1)
  968.       return;
  969.  
  970.     EditorSetFontSize(gFontSizeNames[select.selectedIndex]);
  971.   }
  972. }
  973.  
  974. function onFontSizeChange(fontSizeMenulist, commandID)
  975. {
  976.   // If we don't match anything, set to "0 (normal)"
  977.   var newIndex = 2;
  978.   var size = fontSizeMenulist.getAttribute("size");
  979.   if ( size == "mixed")
  980.   {
  981.     // No single type selected
  982.     newIndex = -1;
  983.   }
  984.   else
  985.   {
  986.     for (var i = 0; i < gFontSizeNames.length; i++)
  987.     {
  988.       if( gFontSizeNames[i] == size )
  989.       {
  990.         newIndex = i;
  991.         break;
  992.       }
  993.     }
  994.   }
  995.   if (fontSizeMenulist.selectedIndex != newIndex)
  996.     fontSizeMenulist.selectedIndex = newIndex;
  997. }
  998.  
  999. function EditorSetFontSize(size)
  1000. {
  1001.   if( size == "0" || size == "normal" ||
  1002.       size == "medium" )
  1003.   {
  1004.     EditorRemoveTextProperty("font", "size");
  1005.     // Also remove big and small,
  1006.     //  else it will seem like size isn't changing correctly
  1007.     EditorRemoveTextProperty("small", "");
  1008.     EditorRemoveTextProperty("big", "");
  1009.   } else {
  1010.     // Temp: convert from new CSS size strings to old HTML size strings
  1011.     switch (size)
  1012.     {
  1013.       case "xx-small":
  1014.       case "x-small":
  1015.         size = "-2";
  1016.         break;
  1017.       case "small":
  1018.         size = "-1";
  1019.         break;
  1020.       case "large":
  1021.         size = "+1";
  1022.         break;
  1023.       case "x-large":
  1024.         size = "+2";
  1025.         break;
  1026.       case "xx-large":
  1027.         size = "+3";
  1028.         break;
  1029.     }
  1030.     EditorSetTextProperty("font", "size", size);
  1031.   }
  1032.   gContentWindow.focus();
  1033. }
  1034.  
  1035. function initFontFaceMenu(menuPopup)
  1036. {
  1037.   initLocalFontFaceMenu(menuPopup);
  1038.  
  1039.   if (menuPopup)
  1040.   {
  1041.     var children = menuPopup.childNodes;
  1042.     if (!children) return;
  1043.  
  1044.     var firstHas = { value: false };
  1045.     var anyHas = { value: false };
  1046.     var allHas = { value: false };
  1047.  
  1048.     // we need to set or clear the checkmark for each menu item since the selection
  1049.     // may be in a new location from where it was when the menu was previously opened
  1050.  
  1051.     // Fixed width (second menu item) is special case: old TT ("teletype") attribute
  1052.     EditorGetTextProperty("tt", "", "", firstHas, anyHas, allHas);
  1053.     children[1].setAttribute("checked", allHas.value);
  1054.  
  1055.     if (!anyHas.value)
  1056.       EditorGetTextProperty("font", "face", "", firstHas, anyHas, allHas);
  1057.  
  1058.     children[0].setAttribute("checked", !anyHas.value);
  1059.  
  1060.     // Skip over default, TT, and separator
  1061.     for (var i = 3; i < children.length; i++)
  1062.     {
  1063.       var menuItem = children[i];
  1064.       var faceType = menuItem.getAttribute("value");
  1065.  
  1066.       if (faceType)
  1067.       {
  1068.         EditorGetTextProperty("font", "face", faceType, firstHas, anyHas, allHas);
  1069.  
  1070.         // Check the menuitem only if all of selection has the face
  1071.         if (allHas.value)
  1072.         {
  1073.           menuItem.setAttribute("checked", "true");
  1074.           break;
  1075.         }
  1076.  
  1077.         // in case none match, make sure we've cleared the checkmark
  1078.         menuItem.removeAttribute("checked");
  1079.       }
  1080.     }
  1081.   }
  1082. }
  1083.  
  1084. const kFixedFontFaceMenuItems = 7; // number of fixed font face menuitems
  1085.  
  1086. function initLocalFontFaceMenu(menuPopup)
  1087. {
  1088.   if (!gLocalFonts)
  1089.   {
  1090.     // Build list of all local fonts once per editor
  1091.     try 
  1092.     {
  1093.       var enumerator = Components.classes["@mozilla.org/gfx/fontenumerator;1"]
  1094.                                  .getService(Components.interfaces.nsIFontEnumerator);
  1095.       var localFontCount = { value: 0 }
  1096.       gLocalFonts = enumerator.EnumerateAllFonts(localFontCount);
  1097.     }
  1098.     catch(e) { }
  1099.   }
  1100.   
  1101.   var useRadioMenuitems = (menuPopup.parentNode.localName == "menu"); // don't do this for menulists  
  1102.   if (menuPopup.childNodes.length == kFixedFontFaceMenuItems) 
  1103.   {
  1104.     if (gLocalFonts.length == 0) {
  1105.       menuPopup.childNodes[kFixedFontFaceMenuItems - 1].hidden = true;
  1106.     }
  1107.     for (var i = 0; i < gLocalFonts.length; ++i)
  1108.     {
  1109.       if (gLocalFonts[i] != "")
  1110.       {
  1111.         var itemNode = document.createElementNS(XUL_NS, "menuitem");
  1112.         itemNode.setAttribute("label", gLocalFonts[i]);
  1113.         itemNode.setAttribute("value", gLocalFonts[i]);
  1114.         if (useRadioMenuitems) {
  1115.           itemNode.setAttribute("type", "radio");
  1116.           itemNode.setAttribute("name", "2");
  1117.           itemNode.setAttribute("observes", "cmd_renderedHTMLEnabler");
  1118.         }
  1119.         menuPopup.appendChild(itemNode);
  1120.       }
  1121.     }
  1122.   }
  1123. }
  1124.  
  1125.  
  1126. function initFontSizeMenu(menuPopup)
  1127. {
  1128.   if (menuPopup)
  1129.   {
  1130.     var children = menuPopup.childNodes;
  1131.     if (!children) return;
  1132.  
  1133.     var firstHas = { value: false };
  1134.     var anyHas = { value: false };
  1135.     var allHas = { value: false };
  1136.  
  1137.     var sizeWasFound = false;
  1138.  
  1139.     // we need to set or clear the checkmark for each menu item since the selection
  1140.     // may be in a new location from where it was when the menu was previously opened
  1141.  
  1142.     // First 2 items add <small> and <big> tags
  1143.     // While it would be better to show the number of levels,
  1144.     //  at least this tells user if either of them are set
  1145.     var menuItem = children[0];
  1146.     if (menuItem)
  1147.     {
  1148.       EditorGetTextProperty("small", "", "", firstHas, anyHas, allHas);
  1149.       menuItem.setAttribute("checked", allHas.value);
  1150.       sizeWasFound = anyHas.value;
  1151.     }
  1152.  
  1153.     menuItem = children[1];
  1154.     if (menuItem)
  1155.     {
  1156.       EditorGetTextProperty("big", "", "", firstHas, anyHas, allHas);
  1157.       menuItem.setAttribute("checked", allHas.value);
  1158.       sizeWasFound |= anyHas.value;
  1159.     }
  1160.  
  1161.     // Fixed size items start after menu separator
  1162.     var menuIndex = 3;
  1163.     // Index of the medium (default) item
  1164.     var mediumIndex = 5;
  1165.  
  1166.     // Scan through all supported "font size" attribute values
  1167.     for (var i = -2; i <= 3; i++)
  1168.     {
  1169.       menuItem = children[menuIndex];
  1170.  
  1171.       // Skip over medium since it'll be set below.
  1172.       // If font size=0 is actually set, we'll toggle it off below if
  1173.       // we enter this loop in this case.
  1174.       if (menuItem && (i != 0))
  1175.       {
  1176.         var sizeString = (i <= 0) ? String(i) : ("+" + String(i));
  1177.         EditorGetTextProperty("font", "size", sizeString, firstHas, anyHas, allHas);
  1178.         // Check the item only if all of selection has the size...
  1179.         menuItem.setAttribute("checked", allHas.value);
  1180.         // ...but remember if ANY of of selection had size set
  1181.         sizeWasFound |= anyHas.value;
  1182.       }
  1183.       menuIndex++;
  1184.     }
  1185.  
  1186.     // if no size was found, then check default (medium)
  1187.     // note that no item is checked in the case of "mixed" selection
  1188.     children[mediumIndex].setAttribute("checked", !sizeWasFound);
  1189.   }
  1190. }
  1191.  
  1192. function onHighlightColorChange()
  1193. {
  1194.   var commandNode = document.getElementById("cmd_highlight");
  1195.   if (commandNode)
  1196.   {
  1197.     var color = commandNode.getAttribute("state");
  1198.     var button = document.getElementById("HighlightColorButton");
  1199.     if (button)
  1200.     {
  1201.       // No color set - get color set on page or other defaults
  1202.       if (!color)
  1203.         color = "transparent" ;
  1204.  
  1205.       button.setAttribute("style", "background-color:"+color+" !important");
  1206.     }
  1207.   }
  1208. }
  1209.  
  1210. function onFontColorChange()
  1211. {
  1212.   var commandNode = document.getElementById("cmd_fontColor");
  1213.   if (commandNode)
  1214.   {
  1215.     var color = commandNode.getAttribute("state");
  1216.     var button = document.getElementById("TextColorButton");
  1217.     if (button)
  1218.     {
  1219.       // No color set - get color set on page or other defaults
  1220.       if (!color)
  1221.         color = gDefaultTextColor;
  1222.       button.setAttribute("style", "background-color:"+color);
  1223.     }
  1224.   }
  1225. }
  1226.  
  1227. function onBackgroundColorChange()
  1228. {
  1229.   var commandNode = document.getElementById("cmd_backgroundColor");
  1230.   if (commandNode)
  1231.   {
  1232.     var color = commandNode.getAttribute("state");
  1233.     var button = document.getElementById("BackgroundColorButton");
  1234.     if (button)
  1235.     {
  1236.       if (!color)
  1237.         color = gDefaultBackgroundColor;
  1238.  
  1239.       button.setAttribute("style", "background-color:"+color);
  1240.     }
  1241.   }
  1242. }
  1243.  
  1244. // Call this when user changes text and/or background colors of the page
  1245. function UpdateDefaultColors()
  1246. {
  1247.   var BrowserColors = GetDefaultBrowserColors();
  1248.   var bodyelement = GetBodyElement();
  1249.   var defTextColor = gDefaultTextColor;
  1250.   var defBackColor = gDefaultBackgroundColor;
  1251.  
  1252.   if (bodyelement)
  1253.   {
  1254.     var color = bodyelement.getAttribute("text");
  1255.     if (color)
  1256.       gDefaultTextColor = color;
  1257.     else if (BrowserColors)
  1258.       gDefaultTextColor = BrowserColors.TextColor;
  1259.  
  1260.     color = bodyelement.getAttribute("bgcolor");
  1261.     if (color)
  1262.       gDefaultBackgroundColor = color;
  1263.     else if (BrowserColors)
  1264.       gDefaultBackgroundColor = BrowserColors.BackgroundColor;
  1265.   }
  1266.  
  1267.   // Trigger update on toolbar
  1268.   if (defTextColor != gDefaultTextColor)
  1269.   {
  1270.     goUpdateCommandState("cmd_fontColor");
  1271.     onFontColorChange();
  1272.   }
  1273.   if (defBackColor != gDefaultBackgroundColor)
  1274.   {
  1275.     goUpdateCommandState("cmd_backgroundColor");
  1276.     onBackgroundColorChange();
  1277.   }
  1278. }
  1279.  
  1280. function GetBackgroundElementWithColor()
  1281. {
  1282.   var editor = GetCurrentTableEditor();
  1283.   if (!editor)
  1284.     return null;
  1285.  
  1286.   gColorObj.Type = "";
  1287.   gColorObj.PageColor = "";
  1288.   gColorObj.TableColor = "";
  1289.   gColorObj.CellColor = "";
  1290.   gColorObj.BackgroundColor = "";
  1291.   gColorObj.SelectedType = "";
  1292.  
  1293.   var tagNameObj = { value: "" };
  1294.   var element;
  1295.   try {
  1296.     element = editor.getSelectedOrParentTableElement(tagNameObj, {value:0});
  1297.   }
  1298.   catch(e) {}
  1299.  
  1300.   if (element && tagNameObj && tagNameObj.value)
  1301.   {
  1302.     gColorObj.BackgroundColor = GetHTMLOrCSSStyleValue(element, "bgcolor", "background-color");
  1303.     gColorObj.BackgroundColor = ConvertRGBColorIntoHEXColor(gColorObj.BackgroundColor);
  1304.     if (tagNameObj.value.toLowerCase() == "td")
  1305.     {
  1306.       gColorObj.Type = "Cell";
  1307.       gColorObj.CellColor = gColorObj.BackgroundColor;
  1308.  
  1309.       // Get any color that might be on parent table
  1310.       var table = GetParentTable(element);
  1311.       gColorObj.TableColor = GetHTMLOrCSSStyleValue(table, "bgcolor", "background-color");
  1312.       gColorObj.TableColor = ConvertRGBColorIntoHEXColor(gColorObj.TableColor);
  1313.     }
  1314.     else
  1315.     {
  1316.       gColorObj.Type = "Table";
  1317.       gColorObj.TableColor = gColorObj.BackgroundColor;
  1318.     }
  1319.     gColorObj.SelectedType = gColorObj.Type;
  1320.   }
  1321.   else
  1322.   {
  1323.     var prefs = GetPrefs();
  1324.     var IsCSSPrefChecked = prefs.getBoolPref("editor.use_css");
  1325.     if (IsCSSPrefChecked && IsHTMLEditor())
  1326.     {
  1327.       var selection = editor.selection;
  1328.       if (selection)
  1329.       {
  1330.         element = selection.focusNode;
  1331.         while (!editor.nodeIsBlock(element))
  1332.           element = element.parentNode;
  1333.       }
  1334.       else
  1335.       {
  1336.         element = GetBodyElement();
  1337.       }
  1338.     }
  1339.     else
  1340.     {
  1341.       element = GetBodyElement();
  1342.     }
  1343.     if (element)
  1344.     {
  1345.       gColorObj.Type = "Page";
  1346.       gColorObj.BackgroundColor = GetHTMLOrCSSStyleValue(element, "bgcolor", "background-color");
  1347.       if (gColorObj.BackgroundColor == "")
  1348.       {
  1349.         gColorObj.BackgroundColor = "transparent";
  1350.       }
  1351.       else
  1352.       {
  1353.         gColorObj.BackgroundColor = ConvertRGBColorIntoHEXColor(gColorObj.BackgroundColor);
  1354.       }
  1355.       gColorObj.PageColor = gColorObj.BackgroundColor;
  1356.     }
  1357.   }
  1358.   return element;
  1359. }
  1360.  
  1361. function SetSmiley(smileyText)
  1362. {
  1363.   try {
  1364.     GetCurrentEditor().insertText(smileyText);
  1365.     gContentWindow.focus();
  1366.   }
  1367.   catch(e) {}
  1368. }
  1369.  
  1370. function EditorSelectColor(colorType, mouseEvent)
  1371. {
  1372.   var editor = GetCurrentEditor();
  1373.   if (!editor || !gColorObj)
  1374.     return;
  1375.  
  1376.   // Shift + mouse click automatically applies last color, if available
  1377.   var useLastColor = mouseEvent ? ( mouseEvent.button == 0 && mouseEvent.shiftKey ) : false;
  1378.   var element;
  1379.   var table;
  1380.   var currentColor = "";
  1381.   var commandNode;
  1382.  
  1383.   if (!colorType)
  1384.     colorType = "";
  1385.  
  1386.   if (colorType == "Text")
  1387.   {
  1388.     gColorObj.Type = colorType;
  1389.  
  1390.     // Get color from command node state
  1391.     commandNode = document.getElementById("cmd_fontColor");
  1392.     currentColor = commandNode.getAttribute("state");
  1393.     currentColor = ConvertRGBColorIntoHEXColor(currentColor);
  1394.     gColorObj.TextColor = currentColor;
  1395.  
  1396.     if (useLastColor && gColorObj.LastTextColor )
  1397.       gColorObj.TextColor = gColorObj.LastTextColor;
  1398.     else
  1399.       useLastColor = false;
  1400.   }
  1401.   else if (colorType == "Highlight")
  1402.   {
  1403.     gColorObj.Type = colorType;
  1404.  
  1405.     // Get color from command node state
  1406.     commandNode = document.getElementById("cmd_highlight");
  1407.     currentColor = commandNode.getAttribute("state");
  1408.     currentColor = ConvertRGBColorIntoHEXColor(currentColor);
  1409.     gColorObj.HighlightColor = currentColor;
  1410.  
  1411.     if (useLastColor && gColorObj.LastHighlightColor )
  1412.       gColorObj.HighlightColor = gColorObj.LastHighlightColor;
  1413.     else
  1414.       useLastColor = false;
  1415.   }
  1416.   else
  1417.   {
  1418.     element = GetBackgroundElementWithColor();
  1419.     if (!element)
  1420.       return;
  1421.  
  1422.     // Get the table if we found a cell
  1423.     if (gColorObj.Type == "Table")
  1424.       table = element;
  1425.     else if (gColorObj.Type == "Cell")
  1426.       table = GetParentTable(element);
  1427.  
  1428.     // Save to avoid resetting if not necessary
  1429.     currentColor = gColorObj.BackgroundColor;
  1430.  
  1431.     if (colorType == "TableOrCell" || colorType == "Cell")
  1432.     {
  1433.       if (gColorObj.Type == "Cell")
  1434.         gColorObj.Type = colorType;
  1435.       else if (gColorObj.Type != "Table")
  1436.         return;
  1437.     }
  1438.     else if (colorType == "Table" && gColorObj.Type == "Page")
  1439.       return;
  1440.  
  1441.     if (colorType == "" && gColorObj.Type == "Cell")
  1442.     {
  1443.       // Using empty string for requested type means
  1444.       //  we can let user select cell or table
  1445.       gColorObj.Type = "TableOrCell";
  1446.     }
  1447.  
  1448.     if (useLastColor && gColorObj.LastBackgroundColor )
  1449.       gColorObj.BackgroundColor = gColorObj.LastBackgroundColor;
  1450.     else
  1451.       useLastColor = false;
  1452.   }
  1453.   // Save the type we are really requesting
  1454.   colorType = gColorObj.Type;
  1455.  
  1456.   if (!useLastColor)
  1457.   {
  1458.     // Avoid the JS warning
  1459.     gColorObj.NoDefault = false;
  1460.  
  1461.     // Launch the ColorPicker dialog
  1462.     // TODO: Figure out how to position this under the color buttons on the toolbar
  1463.     window.openDialog("chrome://editor/content/EdColorPicker.xul", "_blank", "chrome,close,titlebar,modal", "", gColorObj);
  1464.  
  1465.     // User canceled the dialog
  1466.     if (gColorObj.Cancel)
  1467.       return;
  1468.   }
  1469.  
  1470.   if (gColorObj.Type == "Text")
  1471.   {
  1472.     if (currentColor != gColorObj.TextColor)
  1473.     {
  1474.       if (gColorObj.TextColor)
  1475.         EditorSetTextProperty("font", "color", gColorObj.TextColor);
  1476.       else
  1477.         EditorRemoveTextProperty("font", "color");
  1478.     }
  1479.     // Update the command state (this will trigger color button update)
  1480.     goUpdateCommandState("cmd_fontColor");
  1481.   }
  1482.   else if (gColorObj.Type == "Highlight")
  1483.   {
  1484.     if (currentColor != gColorObj.HighlightColor)
  1485.     {
  1486.       if (gColorObj.HighlightColor)
  1487.         EditorSetTextProperty("font", "bgcolor", gColorObj.HighlightColor);
  1488.       else
  1489.         EditorRemoveTextProperty("font", "bgcolor");
  1490.     }
  1491.     // Update the command state (this will trigger color button update)
  1492.     goUpdateCommandState("cmd_highlight");
  1493.   }
  1494.   else if (element)
  1495.   {
  1496.     if (gColorObj.Type == "Table")
  1497.     {
  1498.       // Set background on a table
  1499.       // Note that we shouldn't trust "currentColor" because of "TableOrCell" behavior
  1500.       if (table)
  1501.       {
  1502.         var bgcolor = table.getAttribute("bgcolor");
  1503.         if (bgcolor != gColorObj.BackgroundColor)
  1504.         try {
  1505.           if (gColorObj.BackgroundColor)
  1506.             editor.setAttributeOrEquivalent(table, "bgcolor", gColorObj.BackgroundColor, false);
  1507.           else
  1508.             editor.removeAttributeOrEquivalent(table, "bgcolor", false);
  1509.         } catch (e) {}
  1510.       }
  1511.     }
  1512.     else if (currentColor != gColorObj.BackgroundColor && IsHTMLEditor())
  1513.     {
  1514.       editor.beginTransaction();
  1515.       try
  1516.       {
  1517.         editor.setBackgroundColor(gColorObj.BackgroundColor);
  1518.  
  1519.         if (gColorObj.Type == "Page" && gColorObj.BackgroundColor)
  1520.         {
  1521.           // Set all page colors not explicitly set,
  1522.           //  else you can end up with unreadable pages
  1523.           //  because viewer's default colors may not be same as page author's
  1524.           var bodyelement = GetBodyElement();
  1525.           if (bodyelement)
  1526.           {
  1527.             var defColors = GetDefaultBrowserColors();
  1528.             if (defColors)
  1529.             {
  1530.               if (!bodyelement.getAttribute("text"))
  1531.                 editor.setAttributeOrEquivalent(bodyelement, "text", defColors.TextColor, false);
  1532.  
  1533.               // The following attributes have no individual CSS declaration counterparts
  1534.               // Getting rid of them in favor of CSS implies CSS rules management
  1535.               if (!bodyelement.getAttribute("link"))
  1536.                 editor.setAttribute(bodyelement, "link", defColors.LinkColor);
  1537.  
  1538.               if (!bodyelement.getAttribute("alink"))
  1539.                 editor.setAttribute(bodyelement, "alink", defColors.ActiveLinkColor);
  1540.  
  1541.               if (!bodyelement.getAttribute("vlink"))
  1542.                 editor.setAttribute(bodyelement, "vlink", defColors.VisitedLinkColor);
  1543.             }
  1544.           }
  1545.         }
  1546.       }
  1547.       catch(e) {}
  1548.  
  1549.       editor.endTransaction();
  1550.     }
  1551.  
  1552.     goUpdateCommandState("cmd_backgroundColor");
  1553.   }
  1554.   gContentWindow.focus();
  1555. }
  1556.  
  1557. function GetParentTable(element)
  1558. {
  1559.   var node = element;
  1560.   while (node)
  1561.   {
  1562.     if (node.nodeName.toLowerCase() == "table")
  1563.       return node;
  1564.  
  1565.     node = node.parentNode;
  1566.   }
  1567.   return node;
  1568. }
  1569.  
  1570. function GetParentTableCell(element)
  1571. {
  1572.   var node = element;
  1573.   while (node)
  1574.   {
  1575.     if (node.nodeName.toLowerCase() == "td" || node.nodeName.toLowerCase() == "th")
  1576.       return node;
  1577.  
  1578.     node = node.parentNode;
  1579.   }
  1580.   return node;
  1581. }
  1582.  
  1583. function EditorDblClick(event)
  1584. {
  1585.   // We check event.explicitOriginalTarget here because .target will never
  1586.   // be a textnode (bug 193689)
  1587.   if (event.explicitOriginalTarget)
  1588.   {
  1589.     // Only bring up properties if clicked on an element or selected link
  1590.     var element;
  1591.     try {
  1592.       element = event.explicitOriginalTarget.QueryInterface(
  1593.                     Components.interfaces.nsIDOMElement);
  1594.     } catch (e) {}
  1595.  
  1596.      //  We use "href" instead of "a" to not be fooled by named anchor
  1597.     if (!element)
  1598.       try {
  1599.         element = GetCurrentEditor().getSelectedElement("href");
  1600.       } catch (e) {}
  1601.  
  1602.     if (element)
  1603.     {
  1604.       goDoCommand("cmd_objectProperties");  
  1605.       event.preventDefault();
  1606.     }
  1607.   }
  1608. }
  1609.  
  1610. function EditorClick(event)
  1611. {
  1612.   if (!event)
  1613.     return;
  1614.  
  1615.   if (event.detail == 2)
  1616.   {
  1617.     EditorDblClick(event);
  1618.     return;
  1619.   }
  1620.  
  1621.   // For Web Composer: In Show All Tags Mode,
  1622.   // single click selects entire element,
  1623.   //  except for body and table elements
  1624.   if (IsWebComposer() && event.explicitOriginalTarget && IsHTMLEditor() &&
  1625.       gEditorDisplayMode == kDisplayModeAllTags)
  1626.   {
  1627.     try
  1628.     {
  1629.       // We check event.explicitOriginalTarget here because .target will never
  1630.       // be a textnode (bug 193689)
  1631.       var element = event.explicitOriginalTarget.QueryInterface(
  1632.                         Components.interfaces.nsIDOMElement);
  1633.       var name = element.localName.toLowerCase();
  1634.       if (name != "body" && name != "table" &&
  1635.           name != "td" && name != "th" && name != "caption" && name != "tr")
  1636.       {          
  1637.         GetCurrentEditor().selectElement(event.explicitOriginalTarget);
  1638.         event.preventDefault();
  1639.       }
  1640.     } catch (e) {}
  1641.   }
  1642. }
  1643.  
  1644. /*TODO: We need an oncreate hook to do enabling/disabling for the
  1645.         Format menu. There should be code like this for the
  1646.         object-specific "Properties" item
  1647. */
  1648. // For property dialogs, we want the selected element,
  1649. //  but will accept a parent link, list, or table cell if inside one
  1650. function GetObjectForProperties()
  1651. {
  1652.   var editor = GetCurrentEditor();
  1653.   if (!editor || !IsHTMLEditor())
  1654.     return null;
  1655.  
  1656.   var element;
  1657.   try {
  1658.     element = editor.getSelectedElement("");
  1659.   } catch (e) {}
  1660.   if (element)
  1661.     return element;
  1662.  
  1663.   // Find nearest parent of selection anchor node
  1664.   //   that is a link, list, table cell, or table
  1665.  
  1666.   var anchorNode
  1667.   var node;
  1668.   try {
  1669.     anchorNode = editor.selection.anchorNode;
  1670.     if (anchorNode.firstChild)
  1671.     {
  1672.       // Start at actual selected node
  1673.       var offset = editor.selection.anchorOffset;
  1674.       // Note: If collapsed, offset points to element AFTER caret,
  1675.       //  thus node may be null
  1676.       node = anchorNode.childNodes.item(offset);
  1677.     }
  1678.     if (!node)
  1679.       node = anchorNode;
  1680.   } catch (e) {}
  1681.  
  1682.   while (node)
  1683.   {
  1684.     if (node.nodeName)
  1685.     {
  1686.       var nodeName = node.nodeName.toLowerCase();
  1687.  
  1688.       // Done when we hit the body
  1689.       if (nodeName == "body") break;
  1690.  
  1691.       if ((nodeName == "a" && node.href) ||
  1692.           nodeName == "ol" || nodeName == "ul" || nodeName == "dl" ||
  1693.           nodeName == "td" || nodeName == "th" ||
  1694.           nodeName == "table")
  1695.       {
  1696.         return node;
  1697.       }
  1698.     }
  1699.     node = node.parentNode;
  1700.   }
  1701.   return null;
  1702. }
  1703.  
  1704. function SetEditMode(mode)
  1705. {
  1706.   if (!IsHTMLEditor())
  1707.     return;
  1708.  
  1709.   var bodyElement = GetBodyElement();
  1710.   if (!bodyElement)
  1711.   {
  1712.     dump("SetEditMode: We don't have a body node!\n");
  1713.     return;
  1714.   }
  1715.  
  1716.   // must have editor if here!
  1717.   var editor = GetCurrentEditor();
  1718.  
  1719.   // Switch the UI mode before inserting contents
  1720.   //   so user can't type in source window while new window is being filled
  1721.   var previousMode = gEditorDisplayMode;
  1722.   if (!SetDisplayMode(mode))
  1723.     return;
  1724.  
  1725.   if (mode == kDisplayModeSource)
  1726.   {
  1727.     // Display the DOCTYPE as a non-editable string above edit area
  1728.     var domdoc;
  1729.     try { domdoc = editor.document; } catch (e) { dump( e + "\n");}
  1730.     if (domdoc)
  1731.     {
  1732.       var doctypeNode = document.getElementById("doctype-text");
  1733.       var dt = domdoc.doctype;
  1734.       if (doctypeNode)
  1735.       {
  1736.         if (dt)
  1737.         {
  1738.           doctypeNode.collapsed = false;
  1739.           var doctypeText = "<!DOCTYPE " + domdoc.doctype.name;
  1740.           if (dt.publicId)
  1741.             doctypeText += " PUBLIC \"" + domdoc.doctype.publicId;
  1742.           if (dt.systemId)
  1743.             doctypeText += " "+"\"" + dt.systemId;
  1744.           doctypeText += "\">"
  1745.           doctypeNode.setAttribute("value", doctypeText);
  1746.         }
  1747.         else
  1748.           doctypeNode.collapsed = true;
  1749.       }
  1750.     }
  1751.     // Get the entire document's source string
  1752.  
  1753.     var flags = (editor.documentCharacterSet == "ISO-8859-1")
  1754.       ? 32768  // OutputEncodeLatin1Entities
  1755.       : 16384; // OutputEncodeBasicEntities
  1756.     try { 
  1757.       var encodeEntity = gPrefs.getCharPref("editor.encode_entity");
  1758.       switch (encodeEntity) {
  1759.         case "basic"  : flags = 16384; break; // OutputEncodeBasicEntities
  1760.         case "latin1" : flags = 32768; break; // OutputEncodeLatin1Entities
  1761.         case "html"   : flags = 65536; break; // OutputEncodeHTMLEntities
  1762.         case "none"   : flags = 0;     break;
  1763.       }
  1764.     } catch (e) { }
  1765.  
  1766.     try { 
  1767.       var prettyPrint = gPrefs.getBoolPref("editor.prettyprint");
  1768.       if (prettyPrint)
  1769.         flags |= 2; // OutputFormatted
  1770.  
  1771.     } catch (e) {}
  1772.  
  1773.     flags |= 1024; // OutputLFLineBreak
  1774.     var source = editor.outputToString(kHTMLMimeType, flags);
  1775.     var start = source.search(/<html/i);
  1776.     if (start == -1) start = 0;
  1777.     gSourceTextEditor.insertText(source.slice(start));
  1778.     gSourceTextEditor.resetModificationCount();
  1779.     gSourceTextEditor.addDocumentStateListener(gSourceTextListener);
  1780.     gSourceTextEditor.enableUndo(true);
  1781.     gSourceContentWindow.commandManager.addCommandObserver(gSourceTextObserver, "cmd_undo");
  1782.     gSourceContentWindow.contentWindow.focus();
  1783.     goDoCommand("cmd_moveTop");
  1784.   }
  1785.   else if (previousMode == kDisplayModeSource)
  1786.   {
  1787.     // Only rebuild document if a change was made in source window
  1788.     if (IsHTMLSourceChanged())
  1789.     {
  1790.       // Reduce the undo count so we don't use too much memory
  1791.       //   during multiple uses of source window 
  1792.       //   (reinserting entire doc caches all nodes)
  1793.       try {
  1794.         editor.transactionManager.maxTransactionCount = 1;
  1795.       } catch (e) {}
  1796.  
  1797.       editor.beginTransaction();
  1798.       try {
  1799.         // We are coming from edit source mode,
  1800.         //   so transfer that back into the document
  1801.         source = gSourceTextEditor.outputToString(kTextMimeType, 1024); // OutputLFLineBreak
  1802.         editor.rebuildDocumentFromSource(source);
  1803.  
  1804.         // Get the text for the <title> from the newly-parsed document
  1805.         // (must do this for proper conversion of "escaped" characters)
  1806.         var title = "";
  1807.         var titlenodelist = editor.document.getElementsByTagName("title");
  1808.         if (titlenodelist)
  1809.         {
  1810.           var titleNode = titlenodelist.item(0);
  1811.           if (titleNode && titleNode.firstChild && titleNode.firstChild.data)
  1812.             title = titleNode.firstChild.data;
  1813.         }
  1814.         if (editor.document.title != title)
  1815.           SetDocumentTitle(title);
  1816.  
  1817.       } catch (ex) {
  1818.         dump(ex);
  1819.       }
  1820.       editor.endTransaction();
  1821.  
  1822.       // Restore unlimited undo count
  1823.       try {
  1824.         editor.transactionManager.maxTransactionCount = -1;
  1825.       } catch (e) {}
  1826.     }
  1827.  
  1828.     // Clear out the string buffers
  1829.     gSourceContentWindow.commandManager.removeCommandObserver(gSourceTextObserver, "cmd_undo");
  1830.     gSourceTextEditor.removeDocumentStateListener(gSourceTextListener);
  1831.     gSourceTextEditor.enableUndo(false);
  1832.     gSourceTextEditor.selectAll();
  1833.     gSourceTextEditor.deleteSelection(gSourceTextEditor.eNone);
  1834.     gSourceTextEditor.resetModificationCount();
  1835.  
  1836.     gContentWindow.focus();
  1837.   }
  1838. }
  1839.  
  1840. function CancelHTMLSource()
  1841. {
  1842.   // Don't convert source text back into the DOM document
  1843.   gSourceTextEditor.resetModificationCount();
  1844.   SetDisplayMode(gPreviousNonSourceDisplayMode);
  1845. }
  1846.  
  1847. function FinishHTMLSource()
  1848. {
  1849.   //Here we need to check whether the HTML source contains <head> and <body> tags
  1850.   //Or RebuildDocumentFromSource() will fail.
  1851.   if (IsInHTMLSourceMode())
  1852.   {
  1853.     var htmlSource = gSourceTextEditor.outputToString(kTextMimeType, 1024); // OutputLFLineBreak
  1854.     if (htmlSource.length > 0)
  1855.     {
  1856.       var beginHead = htmlSource.indexOf("<head");
  1857.       if (beginHead == -1)
  1858.       {
  1859.         AlertWithTitle(GetString("Alert"), GetString("NoHeadTag"));
  1860.         //cheat to force back to Source Mode
  1861.         gEditorDisplayMode = kDisplayModePreview;
  1862.         SetDisplayMode(kDisplayModeSource);
  1863.         throw Components.results.NS_ERROR_FAILURE;
  1864.       }
  1865.  
  1866.       var beginBody = htmlSource.indexOf("<body");
  1867.       if (beginBody == -1)
  1868.       {
  1869.         AlertWithTitle(GetString("Alert"), GetString("NoBodyTag"));
  1870.         //cheat to force back to Source Mode
  1871.         gEditorDisplayMode = kDisplayModePreview;
  1872.         SetDisplayMode(kDisplayModeSource);
  1873.         throw Components.results.NS_ERROR_FAILURE;
  1874.       }
  1875.     }
  1876.   }
  1877.  
  1878.   // Switch edit modes -- converts source back into DOM document
  1879.   SetEditMode(gPreviousNonSourceDisplayMode);
  1880. }
  1881.  
  1882. function SetDisplayMode(mode)
  1883. {
  1884.   if (!IsHTMLEditor())
  1885.     return false;
  1886.  
  1887.   // Already in requested mode:
  1888.   //  return false to indicate we didn't switch
  1889.   if (mode == gEditorDisplayMode)
  1890.     return false;
  1891.  
  1892.   var previousMode = gEditorDisplayMode;
  1893.   gEditorDisplayMode = mode;
  1894.  
  1895.   ResetStructToolbar();
  1896.   if (mode == kDisplayModeSource)
  1897.   {
  1898.     // Switch to the sourceWindow (second in the deck)
  1899.     gContentWindowDeck.selectedIndex = 1;
  1900.  
  1901.     //Hide the formatting toolbar if not already hidden
  1902.     gFormatToolbarHidden = gFormatToolbar.hidden;
  1903.     gFormatToolbar.hidden = true;
  1904.     gViewFormatToolbar.hidden = true;
  1905.  
  1906.     gSourceContentWindow.contentWindow.focus();
  1907.   }
  1908.   else
  1909.   {
  1910.     // Save the last non-source mode so we can cancel source editing easily
  1911.     gPreviousNonSourceDisplayMode = mode;
  1912.  
  1913.     // Load/unload appropriate override style sheet
  1914.     try {
  1915.       var editor = GetCurrentEditor();
  1916.       editor.QueryInterface(nsIEditorStyleSheets);
  1917.       editor instanceof Components.interfaces.nsIHTMLObjectResizer;
  1918.  
  1919.       switch (mode)
  1920.       {
  1921.         case kDisplayModePreview:
  1922.           // Disable all extra "edit mode" style sheets 
  1923.           editor.enableStyleSheet(kNormalStyleSheet, false);
  1924.           editor.enableStyleSheet(kAllTagsStyleSheet, false);
  1925.           editor.isImageResizingEnabled = true;
  1926.           break;
  1927.  
  1928.         case kDisplayModeNormal:
  1929.           editor.addOverrideStyleSheet(kNormalStyleSheet);
  1930.           // Disable ShowAllTags mode
  1931.           editor.enableStyleSheet(kAllTagsStyleSheet, false);
  1932.           editor.isImageResizingEnabled = true;
  1933.           break;
  1934.  
  1935.         case kDisplayModeAllTags:
  1936.           editor.addOverrideStyleSheet(kNormalStyleSheet);
  1937.           editor.addOverrideStyleSheet(kAllTagsStyleSheet);
  1938.           // don't allow resizing in AllTags mode because the visible tags
  1939.           // change the computed size of images and tables...
  1940.           if (editor.resizedObject) {
  1941.             editor.hideResizers();
  1942.           }
  1943.           editor.isImageResizingEnabled = false;
  1944.           break;
  1945.       }
  1946.     } catch(e) {}
  1947.  
  1948.     // Switch to the normal editor (first in the deck)
  1949.     gContentWindowDeck.selectedIndex = 0;
  1950.  
  1951.     // Restore menus and toolbars
  1952.     gFormatToolbar.hidden = gFormatToolbarHidden;
  1953.     gViewFormatToolbar.hidden = false;
  1954.  
  1955.     gContentWindow.focus();
  1956.   }
  1957.  
  1958.   // update commands to disable or re-enable stuff
  1959.   window.updateCommands("mode_switch");
  1960.  
  1961.   // Set the selected tab at bottom of window:
  1962.   // (Note: Setting "selectedIndex = mode" won't redraw tabs when menu is used.)
  1963.   document.getElementById("EditModeTabs").selectedItem = document.getElementById(kDisplayModeTabIDS[mode]);
  1964.  
  1965.   // Uncheck previous menuitem and set new check since toolbar may have been used
  1966.   if (previousMode >= 0)
  1967.     document.getElementById(kDisplayModeMenuIDs[previousMode]).setAttribute("checked","false");
  1968.   document.getElementById(kDisplayModeMenuIDs[mode]).setAttribute("checked","true");
  1969.   
  1970.  
  1971.   return true;
  1972. }
  1973.  
  1974. function EditorToggleParagraphMarks()
  1975. {
  1976.   var menuItem = document.getElementById("viewParagraphMarks");
  1977.   if (menuItem)
  1978.   {
  1979.     // Note that the 'type="checbox"' mechanism automatically
  1980.     //  toggles the "checked" state before the oncommand is called,
  1981.     //  so if "checked" is true now, it was just switched to that mode
  1982.     var checked = menuItem.getAttribute("checked");
  1983.     try {
  1984.       var editor = GetCurrentEditor();
  1985.       editor.QueryInterface(nsIEditorStyleSheets);
  1986.  
  1987.       if (checked == "true")
  1988.         editor.addOverrideStyleSheet(kParagraphMarksStyleSheet);
  1989.       else
  1990.         editor.enableStyleSheet(kParagraphMarksStyleSheet, false);
  1991.     }
  1992.     catch(e) { return; }
  1993.   }
  1994. }
  1995.  
  1996. function InitPasteAsMenu()
  1997. {
  1998.   var menuItem = document.getElementById("menu_pasteTable")
  1999.   if(menuItem)
  2000.   {
  2001.     menuItem.IsInTable  
  2002.     menuItem.setAttribute("label", GetString(IsInTable() ? "NestedTable" : "Table"));
  2003.    // menuItem.setAttribute("accesskey",GetString("ObjectPropertiesAccessKey"));
  2004.   }
  2005.   // TODO: Do enabling based on what is in the clipboard
  2006. }
  2007.  
  2008. function UpdateWindowTitle()
  2009. {
  2010.   try {
  2011.     var windowTitle = GetDocumentTitle();
  2012.     if (!windowTitle)
  2013.       windowTitle = GetString("untitled");
  2014.  
  2015.     // Append just the 'leaf' filename to the Doc. Title for the window caption
  2016.     var docUrl = GetDocumentUrl();
  2017.     if (docUrl && !IsUrlAboutBlank(docUrl))
  2018.     {
  2019.       var scheme = GetScheme(docUrl);
  2020.       var filename = GetFilename(docUrl);
  2021.       if (filename)
  2022.         windowTitle += " [" + scheme + ":/.../" + filename + "]";
  2023.  
  2024.       // Save changed title in the recent pages data in prefs
  2025.       SaveRecentFilesPrefs();
  2026.     }
  2027.     // Set window title with " - Composer" appended
  2028.     xulWin = document.documentElement;
  2029.     window.title = windowTitle + xulWin.getAttribute("titlemenuseparator") + 
  2030.                    xulWin.getAttribute("titlemodifier");
  2031.   } catch (e) { dump(e); }
  2032. }
  2033.  
  2034. function BuildRecentPagesMenu()
  2035. {
  2036.   var editor = GetCurrentEditor();
  2037.   if (!editor || !gPrefs)
  2038.     return;
  2039.  
  2040.   var popup = document.getElementById("menupopup_RecentFiles");
  2041.   if (!popup || !editor.document)
  2042.     return;
  2043.  
  2044.   // Delete existing menu
  2045.   while (popup.firstChild)
  2046.     popup.removeChild(popup.firstChild);
  2047.  
  2048.   // Current page is the "0" item in the list we save in prefs,
  2049.   //  but we don't include it in the menu.
  2050.   var curUrl = StripPassword(GetDocumentUrl());
  2051.   var historyCount = 10;
  2052.   try {
  2053.     historyCount = gPrefs.getIntPref("editor.history.url_maximum");
  2054.   } catch(e) {}
  2055.   var menuIndex = 1;
  2056.  
  2057.   for (var i = 0; i < historyCount; i++)
  2058.   {
  2059.     var url = GetUnicharPref("editor.history_url_"+i);
  2060.  
  2061.     // Skip over current url
  2062.     if (url && url != curUrl)
  2063.     {
  2064.       // Build the menu
  2065.       var title = GetUnicharPref("editor.history_title_"+i);
  2066.       AppendRecentMenuitem(popup, title, url, menuIndex);
  2067.       menuIndex++;
  2068.     }
  2069.   }
  2070. }
  2071.  
  2072. function SaveRecentFilesPrefs()
  2073. {
  2074.   // Can't do anything if no prefs
  2075.   if (!gPrefs) return;
  2076.  
  2077.   var curUrl = StripPassword(GetDocumentUrl());
  2078.   var historyCount = 10;
  2079.   try {
  2080.     historyCount = gPrefs.getIntPref("editor.history.url_maximum"); 
  2081.   } catch(e) {}
  2082.  
  2083.   var titleArray = [];
  2084.   var urlArray = [];
  2085.  
  2086.   if (historyCount && !IsUrlAboutBlank(curUrl) &&  GetScheme(curUrl) != "data")
  2087.   {
  2088.     titleArray.push(GetDocumentTitle());
  2089.     urlArray.push(curUrl);
  2090.   }
  2091.  
  2092.   for (var i = 0; i < historyCount && urlArray.length < historyCount; i++)
  2093.   {
  2094.     var url = GetUnicharPref("editor.history_url_"+i);
  2095.  
  2096.     // Continue if URL pref is missing because 
  2097.     //  a URL not found during loading may have been removed
  2098.  
  2099.     // Skip over current an "data" URLs
  2100.     if (url && url != curUrl && GetScheme(url) != "data")
  2101.     {
  2102.       var title = GetUnicharPref("editor.history_title_"+i);
  2103.       titleArray.push(title);
  2104.       urlArray.push(url);
  2105.     }
  2106.   }
  2107.  
  2108.   // Resave the list back to prefs in the new order
  2109.   for (i = 0; i < urlArray.length; i++)
  2110.   {
  2111.     SetUnicharPref("editor.history_title_"+i, titleArray[i]);
  2112.     SetUnicharPref("editor.history_url_"+i, urlArray[i]);
  2113.   }
  2114. }
  2115.  
  2116. function AppendRecentMenuitem(menupopup, title, url, menuIndex)
  2117. {
  2118.   if (menupopup)
  2119.   {
  2120.     var menuItem = document.createElementNS(XUL_NS, "menuitem");
  2121.     if (menuItem)
  2122.     {
  2123.       var accessKey;
  2124.       if (menuIndex <= 9)
  2125.         accessKey = String(menuIndex);
  2126.       else if (menuIndex == 10)
  2127.         accessKey = "0";
  2128.       else
  2129.         accessKey = " ";
  2130.  
  2131.       var itemString = accessKey+" ";
  2132.  
  2133.       // Show "title [url]" or just the URL
  2134.       if (title)
  2135.       {
  2136.        itemString += title;
  2137.        itemString += " [";
  2138.       }
  2139.       itemString += url;
  2140.       if (title)
  2141.         itemString += "]";
  2142.  
  2143.       menuItem.setAttribute("label", itemString);
  2144.       menuItem.setAttribute("crop", "center");
  2145.       menuItem.setAttribute("value", url);
  2146.       if (accessKey != " ")
  2147.         menuItem.setAttribute("accesskey", accessKey);
  2148.       menupopup.appendChild(menuItem);
  2149.     }
  2150.   }
  2151. }
  2152.  
  2153. function EditorInitFileMenu()
  2154. {
  2155.   // Disable "Save" menuitem when editing remote url. User should use "Save As"
  2156.   var docUrl = GetDocumentUrl();
  2157.   var scheme = GetScheme(docUrl);
  2158.   if (scheme && scheme != "file")
  2159.     SetElementEnabledById("saveMenuitem", false);
  2160.  
  2161.   // Enable recent pages submenu if there are any history entries in prefs
  2162.   var historyUrl = "";
  2163.  
  2164.   var historyCount = 10;
  2165.   try { historyCount = gPrefs.getIntPref("editor.history.url_maximum"); } catch(e) {}
  2166.   if (historyCount)
  2167.   {
  2168.     historyUrl = GetUnicharPref("editor.history_url_0");
  2169.     
  2170.     // See if there's more if current file is only entry in history list
  2171.     if (historyUrl && historyUrl == docUrl)
  2172.       historyUrl = GetUnicharPref("editor.history_url_1");
  2173.   }
  2174.   SetElementEnabledById("menu_RecentFiles", historyUrl != "");
  2175. }
  2176.  
  2177. function EditorInitFormatMenu()
  2178. {
  2179.   try {
  2180.     InitObjectPropertiesMenuitem("objectProperties");
  2181.     InitRemoveStylesMenuitems("removeStylesMenuitem", "removeLinksMenuitem", "removeNamedAnchorsMenuitem");
  2182.   } catch(ex) {}
  2183. }
  2184.  
  2185. function InitObjectPropertiesMenuitem(id)
  2186. {
  2187.   // Set strings and enable for the [Object] Properties item
  2188.   // Note that we directly do the enabling instead of
  2189.   //  using goSetCommandEnabled since we already have the menuitem
  2190.   var menuItem = document.getElementById(id);
  2191.   if (!menuItem) return null;
  2192.  
  2193.   var element;
  2194.   var menuStr = GetString("AdvancedProperties");
  2195.   var name;
  2196.  
  2197.   if (IsEditingRenderedHTML())
  2198.     element = GetObjectForProperties();
  2199.  
  2200.   if (element && element.nodeName)
  2201.   {
  2202.     var objStr = "";
  2203.     menuItem.setAttribute("disabled", "");
  2204.     name = element.nodeName.toLowerCase();
  2205.     switch (name)
  2206.     {
  2207.       case "img":
  2208.         // Check if img is enclosed in link
  2209.         //  (use "href" to not be fooled by named anchor)
  2210.         try
  2211.         {
  2212.           if (GetCurrentEditor().getElementOrParentByTagName("href", element))
  2213.             objStr = GetString("ImageAndLink");
  2214.         } catch(e) {}
  2215.         
  2216.         if (objStr == "")
  2217.           objStr = GetString("Image");
  2218.         break;
  2219.       case "hr":
  2220.         objStr = GetString("HLine");
  2221.         break;
  2222.       case "table":
  2223.         objStr = GetString("Table");
  2224.         break;
  2225.       case "th":
  2226.         name = "td";
  2227.       case "td":
  2228.         objStr = GetString("TableCell");
  2229.         break;
  2230.       case "ol":
  2231.       case "ul":
  2232.       case "dl":
  2233.         objStr = GetString("List");
  2234.         break;
  2235.       case "li":
  2236.         objStr = GetString("ListItem");
  2237.         break;
  2238.       case "form":
  2239.         objStr = GetString("Form");
  2240.         break;
  2241.       case "input":
  2242.         var type = element.getAttribute("type");
  2243.         if (type && type.toLowerCase() == "image")
  2244.           objStr = GetString("InputImage");
  2245.         else
  2246.           objStr = GetString("InputTag");
  2247.         break;
  2248.       case "textarea":
  2249.         objStr = GetString("TextArea");
  2250.         break;
  2251.       case "select":
  2252.         objStr = GetString("Select");
  2253.         break;
  2254.       case "button":
  2255.         objStr = GetString("Button");
  2256.         break;
  2257.       case "label":
  2258.         objStr = GetString("Label");
  2259.         break;
  2260.       case "fieldset":
  2261.         objStr = GetString("FieldSet");
  2262.         break;
  2263.       case "a":
  2264.         if (element.name)
  2265.         {
  2266.           objStr = GetString("NamedAnchor");
  2267.           name = "anchor";
  2268.         }
  2269.         else if(element.href)
  2270.         {
  2271.           objStr = GetString("Link");
  2272.           name = "href";
  2273.         }
  2274.         break;
  2275.     }
  2276.     if (objStr)
  2277.       menuStr = GetString("ObjectProperties").replace(/%obj%/,objStr);
  2278.   }
  2279.   else
  2280.   {
  2281.     // We show generic "Properties" string, but disable menu item
  2282.     menuItem.setAttribute("disabled","true");
  2283.   }
  2284.   menuItem.setAttribute("label", menuStr);
  2285.   menuItem.setAttribute("accesskey",GetString("ObjectPropertiesAccessKey"));
  2286.   return name;
  2287. }
  2288.  
  2289. function InitParagraphMenu()
  2290. {
  2291.   var mixedObj = { value: null };
  2292.   var state;
  2293.   try {
  2294.     state = GetCurrentEditor().getParagraphState(mixedObj);
  2295.   }
  2296.   catch(e) {}
  2297.   var IDSuffix;
  2298.  
  2299.   // PROBLEM: When we get blockquote, it masks other styles contained by it
  2300.   // We need a separate method to get blockquote state
  2301.  
  2302.   // We use "x" as uninitialized paragraph state
  2303.   if (!state || state == "x")
  2304.     IDSuffix = "bodyText" // No paragraph container
  2305.   else
  2306.     IDSuffix = state;
  2307.  
  2308.   // Set "radio" check on one item, but...
  2309.   var menuItem = document.getElementById("menu_"+IDSuffix);
  2310.   menuItem.setAttribute("checked", "true");
  2311.  
  2312.   // ..."bodyText" is returned if mixed selection, so remove checkmark
  2313.   if (mixedObj.value)
  2314.     menuItem.setAttribute("checked", "false");
  2315. }
  2316.  
  2317. function GetListStateString()
  2318. {
  2319.   try {
  2320.     var editor = GetCurrentEditor();
  2321.  
  2322.     var mixedObj = { value: null };
  2323.     var hasOL = { value: false };
  2324.     var hasUL = { value: false };
  2325.     var hasDL = { value: false };
  2326.     editor.getListState(mixedObj, hasOL, hasUL, hasDL);
  2327.  
  2328.     if (mixedObj.value)
  2329.       return "mixed";
  2330.     if (hasOL.value)
  2331.       return "ol";
  2332.     if (hasUL.value)
  2333.       return "ul";
  2334.  
  2335.     if (hasDL.value)
  2336.     {
  2337.       var hasLI = { value: false };
  2338.       var hasDT = { value: false };
  2339.       var hasDD = { value: false };
  2340.       editor.getListItemState(mixedObj, hasLI, hasDT, hasDD);
  2341.       if (mixedObj.value)
  2342.         return "mixed";
  2343.       if (hasLI.value)
  2344.         return "li";
  2345.       if (hasDT.value)
  2346.         return "dt";
  2347.       if (hasDD.value)
  2348.         return "dd";
  2349.     }
  2350.   } catch (e) {}
  2351.  
  2352.   // return "noList" if we aren't in a list at all
  2353.   return "noList";
  2354. }
  2355.  
  2356. function InitListMenu()
  2357. {
  2358.   if (!IsHTMLEditor())
  2359.     return;
  2360.  
  2361.   var IDSuffix = GetListStateString();
  2362.  
  2363.   // Set enable state for the "None" menuitem
  2364.   goSetCommandEnabled("cmd_removeList", IDSuffix != "noList");
  2365.  
  2366.   // Set "radio" check on one item, but...
  2367.   // we won't find a match if it's "mixed"
  2368.   var menuItem = document.getElementById("menu_"+IDSuffix);
  2369.   if (menuItem)
  2370.     menuItem.setAttribute("checked", "true");
  2371. }
  2372.  
  2373. function GetAlignmentString()
  2374. {
  2375.   var mixedObj = { value: null };
  2376.   var alignObj = { value: null };
  2377.   try {
  2378.     GetCurrentEditor().getAlignment(mixedObj, alignObj);
  2379.   } catch (e) {}
  2380.  
  2381.   if (mixedObj.value)
  2382.     return "mixed";
  2383.   if (alignObj.value == nsIHTMLEditor.eLeft)
  2384.     return "left";
  2385.   if (alignObj.value == nsIHTMLEditor.eCenter)
  2386.     return "center";
  2387.   if (alignObj.value == nsIHTMLEditor.eRight)
  2388.     return "right";
  2389.   if (alignObj.value == nsIHTMLEditor.eJustify)
  2390.     return "justify";
  2391.  
  2392.   // return "left" if we got here
  2393.   return "left";
  2394. }
  2395.  
  2396. function InitAlignMenu()
  2397. {
  2398.   if (!IsHTMLEditor())
  2399.     return;
  2400.  
  2401.   var IDSuffix = GetAlignmentString();
  2402.  
  2403.   // we won't find a match if it's "mixed"
  2404.   var menuItem = document.getElementById("menu_"+IDSuffix);
  2405.   if (menuItem)
  2406.     menuItem.setAttribute("checked", "true");
  2407. }
  2408.  
  2409. function EditorSetDefaultPrefsAndDoctype()
  2410. {
  2411.   var editor = GetCurrentEditor();
  2412.  
  2413.   var domdoc;
  2414.   try { 
  2415.     domdoc = editor.document;
  2416.   } catch (e) { dump( e + "\n"); }
  2417.   if ( !domdoc )
  2418.   {
  2419.     dump("EditorSetDefaultPrefsAndDoctype: EDITOR DOCUMENT NOT FOUND\n");
  2420.     return;
  2421.   }
  2422.  
  2423.   // Insert a doctype element 
  2424.   // if it is missing from existing doc
  2425.   if (!domdoc.doctype)
  2426.   {
  2427.     var newdoctype = domdoc.implementation.createDocumentType("HTML", "-//W3C//DTD HTML 4.01 Transitional//EN","");
  2428.     if (newdoctype)
  2429.       domdoc.insertBefore(newdoctype, domdoc.firstChild);
  2430.   }
  2431.   
  2432.   // search for head; we'll need this for meta tag additions
  2433.   var headelement = 0;
  2434.   var headnodelist = domdoc.getElementsByTagName("head");
  2435.   if (headnodelist)
  2436.   {
  2437.     var sz = headnodelist.length;
  2438.     if ( sz >= 1 )
  2439.       headelement = headnodelist.item(0);
  2440.   }
  2441.   else
  2442.   {
  2443.     headelement = domdoc.createElement("head");
  2444.     if (headelement)
  2445.       domdoc.insertAfter(headelement, domdoc.firstChild);
  2446.   }
  2447.  
  2448.   /* only set default prefs for new documents */
  2449.   if (!IsUrlAboutBlank(GetDocumentUrl()))
  2450.     return;
  2451.  
  2452.   // search for author meta tag.
  2453.   // if one is found, don't do anything.
  2454.   // if not, create one and make it a child of the head tag
  2455.   //   and set its content attribute to the value of the editor.author preference.
  2456.  
  2457.   var nodelist = domdoc.getElementsByTagName("meta");
  2458.   if ( nodelist )
  2459.   {
  2460.     // we should do charset first since we need to have charset before
  2461.     // hitting other 8-bit char in other meta tags
  2462.     // grab charset pref and make it the default charset
  2463.     var element;
  2464.     var prefCharsetString = 0;
  2465.     try
  2466.     {
  2467.       prefCharsetString = gPrefs.getComplexValue("intl.charset.default",
  2468.                                                  Components.interfaces.nsIPrefLocalizedString).data;
  2469.     }
  2470.     catch (ex) {}
  2471.     if ( prefCharsetString && prefCharsetString != 0)
  2472.     {
  2473.         element = domdoc.createElement("meta");
  2474.         if ( element )
  2475.         {
  2476.           element.setAttribute("http-equiv", "content-type");
  2477.           element.setAttribute("content", "text/html; charset=" + prefCharsetString);
  2478.           headelement.insertBefore( element, headelement.firstChild );
  2479.         }
  2480.     }
  2481.  
  2482.     var node = 0;
  2483.     var listlength = nodelist.length;
  2484.  
  2485.     // let's start by assuming we have an author in case we don't have the pref
  2486.     var authorFound = false;
  2487.     for (var i = 0; i < listlength && !authorFound; i++)
  2488.     {
  2489.       node = nodelist.item(i);
  2490.       if ( node )
  2491.       {
  2492.         var value = node.getAttribute("name");
  2493.         if (value && value.toLowerCase() == "author")
  2494.         {
  2495.           authorFound = true;
  2496.         }
  2497.       }
  2498.     }
  2499.  
  2500.     var prefAuthorString = 0;
  2501.     try
  2502.     {
  2503.       prefAuthorString = gPrefs.getComplexValue("editor.author",
  2504.                                                 Components.interfaces.nsISupportsString).data;
  2505.     }
  2506.     catch (ex) {}
  2507.     if ( prefAuthorString && prefAuthorString != 0)
  2508.     {
  2509.       if ( !authorFound && headelement)
  2510.       {
  2511.         /* create meta tag with 2 attributes */
  2512.         element = domdoc.createElement("meta");
  2513.         if ( element )
  2514.         {
  2515.           element.setAttribute("name", "author");
  2516.           element.setAttribute("content", prefAuthorString);
  2517.           headelement.appendChild( element );
  2518.         }
  2519.       }
  2520.     }
  2521.   }
  2522.  
  2523.   // add title tag if not present
  2524.   var titlenodelist = editor.document.getElementsByTagName("title");
  2525.   if (headelement && titlenodelist && titlenodelist.length == 0)
  2526.   {
  2527.      titleElement = domdoc.createElement("title");
  2528.      if (titleElement)
  2529.        headelement.appendChild(titleElement);
  2530.   }
  2531.  
  2532.   // Get editor color prefs
  2533.   var use_custom_colors = false;
  2534.   try {
  2535.     use_custom_colors = gPrefs.getBoolPref("editor.use_custom_colors");
  2536.   }
  2537.   catch (ex) {}
  2538.  
  2539.   // find body node
  2540.   var bodyelement = GetBodyElement();
  2541.   if (bodyelement)
  2542.   {
  2543.     if ( use_custom_colors )
  2544.     {
  2545.       // try to get the default color values.  ignore them if we don't have them.
  2546.       var text_color;
  2547.       var link_color;
  2548.       var active_link_color;
  2549.       var followed_link_color;
  2550.       var background_color;
  2551.  
  2552.       try { text_color = gPrefs.getCharPref("editor.text_color"); } catch (e) {}
  2553.       try { link_color = gPrefs.getCharPref("editor.link_color"); } catch (e) {}
  2554.       try { active_link_color = gPrefs.getCharPref("editor.active_link_color"); } catch (e) {}
  2555.       try { followed_link_color = gPrefs.getCharPref("editor.followed_link_color"); } catch (e) {}
  2556.       try { background_color = gPrefs.getCharPref("editor.background_color"); } catch(e) {}
  2557.  
  2558.       // add the color attributes to the body tag.
  2559.       // and use them for the default text and background colors if not empty
  2560.       try {
  2561.         if (text_color)
  2562.         {
  2563.           editor.setAttributeOrEquivalent(bodyelement, "text", text_color, true);
  2564.           gDefaultTextColor = text_color;
  2565.         }
  2566.         if (background_color)
  2567.         {
  2568.           editor.setAttributeOrEquivalent(bodyelement, "bgcolor", background_color, true);
  2569.           gDefaultBackgroundColor = background_color
  2570.         }
  2571.  
  2572.         if (link_color)
  2573.           bodyelement.setAttribute("link", link_color);
  2574.         if (active_link_color)
  2575.           bodyelement.setAttribute("alink", active_link_color);
  2576.         if (followed_link_color)
  2577.           bodyelement.setAttribute("vlink", followed_link_color);
  2578.       } catch (e) {}
  2579.     }
  2580.     // Default image is independent of Custom colors???
  2581.     try {
  2582.       var background_image = gPrefs.getCharPref("editor.default_background_image");
  2583.       if (background_image)
  2584.         editor.setAttributeOrEquivalent(bodyelement, "background", background_image, true);
  2585.     } catch (e) {dump("BACKGROUND EXCEPTION: "+e+"\n"); }
  2586.  
  2587.   }
  2588.   // auto-save???
  2589. }
  2590.  
  2591. function GetBodyElement()
  2592. {
  2593.   try {
  2594.     return GetCurrentEditor().rootElement;
  2595.   }
  2596.   catch (ex) {
  2597.     dump("no body tag found?!\n");
  2598.     //  better have one, how can we blow things up here?
  2599.   }
  2600.   return null;
  2601. }
  2602.  
  2603. // --------------------------- Logging stuff ---------------------------
  2604.  
  2605. function EditorGetNodeFromOffsets(offsets)
  2606. {
  2607.   var node = null;
  2608.   try {
  2609.     node = GetCurrentEditor().document;
  2610.  
  2611.     for (var i = 0; i < offsets.length; i++)
  2612.       node = node.childNodes[offsets[i]];
  2613.   } catch (e) {}
  2614.   return node;
  2615. }
  2616.  
  2617. function EditorSetSelectionFromOffsets(selRanges)
  2618. {
  2619.   try {
  2620.     var editor = GetCurrentEditor();
  2621.     var selection = editor.selection;
  2622.     selection.removeAllRanges();
  2623.  
  2624.     var rangeArr, start, end, node, offset;
  2625.     for (var i = 0; i < selRanges.length; i++)
  2626.     {
  2627.       rangeArr = selRanges[i];
  2628.       start    = rangeArr[0];
  2629.       end      = rangeArr[1];
  2630.  
  2631.       var range = editor.document.createRange();
  2632.  
  2633.       node   = EditorGetNodeFromOffsets(start[0]);
  2634.       offset = start[1];
  2635.  
  2636.       range.setStart(node, offset);
  2637.  
  2638.       node   = EditorGetNodeFromOffsets(end[0]);
  2639.       offset = end[1];
  2640.  
  2641.       range.setEnd(node, offset);
  2642.  
  2643.       selection.addRange(range);
  2644.     }
  2645.   } catch (e) {}
  2646. }
  2647.  
  2648. //--------------------------------------------------------------------
  2649. function initFontStyleMenu(menuPopup)
  2650. {
  2651.   for (var i = 0; i < menuPopup.childNodes.length; i++)
  2652.   {
  2653.     var menuItem = menuPopup.childNodes[i];
  2654.     var theStyle = menuItem.getAttribute("state");
  2655.     if (theStyle)
  2656.     {
  2657.       menuItem.setAttribute("checked", theStyle);
  2658.     }
  2659.   }
  2660. }
  2661.  
  2662. //--------------------------------------------------------------------
  2663. function onButtonUpdate(button, commmandID)
  2664. {
  2665.   var commandNode = document.getElementById(commmandID);
  2666.   var state = commandNode.getAttribute("state");
  2667.   button.checked = state == "true";
  2668. }
  2669.  
  2670. //--------------------------------------------------------------------
  2671. function onStateButtonUpdate(button, commmandID, onState)
  2672. {
  2673.   var commandNode = document.getElementById(commmandID);
  2674.   var state = commandNode.getAttribute("state");
  2675.  
  2676.   button.checked = state == onState;
  2677. }
  2678.  
  2679. // --------------------------- Status calls ---------------------------
  2680. function getColorAndSetColorWell(ColorPickerID, ColorWellID)
  2681. {
  2682.   var colorWell;
  2683.   if (ColorWellID)
  2684.     colorWell = document.getElementById(ColorWellID);
  2685.  
  2686.   var colorPicker = document.getElementById(ColorPickerID);
  2687.   if (colorPicker)
  2688.   {
  2689.     // Extract color from colorPicker and assign to colorWell.
  2690.     var color = colorPicker.getAttribute("color");
  2691.  
  2692.     if (colorWell && color)
  2693.     {
  2694.       // Use setAttribute so colorwell can be a XUL element, such as button
  2695.       colorWell.setAttribute("style", "background-color: " + color);
  2696.     }
  2697.   }
  2698.   return color;
  2699. }
  2700.  
  2701. //-----------------------------------------------------------------------------------
  2702. function IsSpellCheckerInstalled()
  2703. {
  2704.   return "@mozilla.org/spellchecker;1" in Components.classes;
  2705. }
  2706.  
  2707. //-----------------------------------------------------------------------------------
  2708. function IsFindInstalled()
  2709. {
  2710.   return "@mozilla.org/embedcomp/rangefind;1" in Components.classes
  2711.           && "@mozilla.org/find/find_service;1" in Components.classes;
  2712. }
  2713.  
  2714. //-----------------------------------------------------------------------------------
  2715. function RemoveInapplicableUIElements()
  2716. {
  2717.   // For items that are in their own menu block, remove associated separator
  2718.   // (we can't use "hidden" since class="hide-in-IM" CSS rule interferes)
  2719.  
  2720.    // if no find, remove find ui
  2721.   if (!IsFindInstalled())
  2722.   {
  2723.     HideItem("menu_find");
  2724.     HideItem("menu_findnext");
  2725.     HideItem("menu_replace");
  2726.     HideItem("menu_find");
  2727.     RemoveItem("sep_find");
  2728.   }
  2729.  
  2730.    // if no spell checker, remove spell checker ui
  2731.   if (!IsSpellCheckerInstalled())
  2732.   {
  2733.     HideItem("spellingButton");
  2734.     HideItem("menu_checkspelling");
  2735.     RemoveItem("sep_checkspelling");
  2736.   }
  2737.   else
  2738.   {
  2739.     SetElementEnabled(document.getElementById("menu_checkspelling"), true);
  2740.     SetElementEnabled(document.getElementById("spellingButton"), true);
  2741.     SetElementEnabled(document.getElementById("checkspellingkb"), true);
  2742.   }
  2743.  
  2744.   // Remove menu items (from overlay shared with HTML editor) in non-HTML.
  2745.   if (!IsHTMLEditor())
  2746.   {
  2747.     HideItem("insertAnchor");
  2748.     HideItem("insertImage");
  2749.     HideItem("insertHline");
  2750.     HideItem("insertTable");
  2751.     HideItem("insertHTML");
  2752.     HideItem("insertFormMenu");
  2753.     HideItem("fileExportToText");
  2754.     HideItem("viewFormatToolbar");
  2755.     HideItem("viewEditModeToolbar");
  2756.   }
  2757. }
  2758.  
  2759. function HideItem(id)
  2760. {
  2761.   var item = document.getElementById(id);
  2762.   if (item)
  2763.     item.hidden = true;
  2764. }
  2765.  
  2766. function RemoveItem(id)
  2767. {
  2768.   var item = document.getElementById(id);
  2769.   if (item)
  2770.     item.parentNode.removeChild(item);
  2771. }
  2772.  
  2773. // Command Updating Strategy:
  2774. //   Don't update on on selection change, only when menu is displayed,
  2775. //   with this "oncreate" hander:
  2776. function EditorInitTableMenu()
  2777. {
  2778.   try {
  2779.     InitJoinCellMenuitem("menu_JoinTableCells");
  2780.   } catch (ex) {}
  2781.  
  2782.   // Set enable states for all table commands
  2783.   goUpdateTableMenuItems(document.getElementById("composerTableMenuItems"));
  2784. }
  2785.  
  2786. function InitJoinCellMenuitem(id)
  2787. {
  2788.   // Change text on the "Join..." item depending if we
  2789.   //   are joining selected cells or just cell to right
  2790.   // TODO: What to do about normal selection that crosses
  2791.   //       table border? Try to figure out all cells
  2792.   //       included in the selection?
  2793.   var menuText;
  2794.   var menuItem = document.getElementById(id);
  2795.   if (!menuItem) return;
  2796.  
  2797.   // Use "Join selected cells if there's more than 1 cell selected
  2798.   var numSelected;
  2799.   var foundElement;
  2800.   
  2801.   try {
  2802.     var tagNameObj = {};
  2803.     var countObj = {value:0}
  2804.     foundElement = GetCurrentTableEditor().getSelectedOrParentTableElement(tagNameObj, countObj);
  2805.     numSelected = countObj.value
  2806.   }
  2807.   catch(e) {}
  2808.   if (foundElement && numSelected > 1)
  2809.     menuText = GetString("JoinSelectedCells");
  2810.   else
  2811.     menuText = GetString("JoinCellToRight");
  2812.  
  2813.   menuItem.setAttribute("label",menuText);
  2814.   menuItem.setAttribute("accesskey",GetString("JoinCellAccesskey"));
  2815. }
  2816.  
  2817. function InitRemoveStylesMenuitems(removeStylesId, removeLinksId, removeNamedAnchorsId)
  2818. {
  2819.   var editor = GetCurrentEditor();
  2820.   if (!editor)
  2821.     return;
  2822.  
  2823.   // Change wording of menuitems depending on selection
  2824.   var stylesItem = document.getElementById(removeStylesId);
  2825.   var linkItem = document.getElementById(removeLinksId);
  2826.  
  2827.   var isCollapsed = editor.selection.isCollapsed;
  2828.   if (stylesItem)
  2829.   {
  2830.     stylesItem.setAttribute("label", isCollapsed ? GetString("StopTextStyles") : GetString("RemoveTextStyles"));
  2831.     stylesItem.setAttribute("accesskey", GetString("RemoveTextStylesAccesskey"));
  2832.   }
  2833.   if (linkItem)
  2834.   {
  2835.     linkItem.setAttribute("label", isCollapsed ? GetString("StopLinks") : GetString("RemoveLinks"));
  2836.     linkItem.setAttribute("accesskey", GetString("RemoveLinksAccesskey"));
  2837.     // Note: disabling text style is a pain since there are so many - forget it!
  2838.  
  2839.     // Disable if not in a link, but always allow "Remove"
  2840.     //  if selection isn't collapsed since we only look at anchor node
  2841.     try {
  2842.       SetElementEnabled(linkItem, !isCollapsed ||
  2843.                       editor.getElementOrParentByTagName("href", null));
  2844.     } catch(e) {}      
  2845.   }
  2846.   // Disable if selection is collapsed
  2847.   SetElementEnabledById(removeNamedAnchorsId, !isCollapsed);
  2848. }
  2849.  
  2850. function goUpdateTableMenuItems(commandset)
  2851. {
  2852.   var editor = GetCurrentTableEditor();
  2853.   if (!editor)
  2854.   {
  2855.     dump("goUpdateTableMenuItems: too early, not initialized\n");
  2856.     return;
  2857.   }
  2858.  
  2859.   var enabled = false;
  2860.   var enabledIfTable = false;
  2861.  
  2862.   var flags = editor.flags;
  2863.   if (!(flags & nsIPlaintextEditor.eEditorReadonlyMask) &&
  2864.       IsEditingRenderedHTML())
  2865.   {
  2866.     var tagNameObj = { value: "" };
  2867.     var element;
  2868.     try {
  2869.       element = editor.getSelectedOrParentTableElement(tagNameObj, {value:0});
  2870.     }
  2871.     catch(e) {}
  2872.  
  2873.     if (element)
  2874.     {
  2875.       // Value when we need to have a selected table or inside a table
  2876.       enabledIfTable = true;
  2877.  
  2878.       // All others require being inside a cell or selected cell
  2879.       enabled = (tagNameObj.value == "td");
  2880.     }
  2881.   }
  2882.  
  2883.   // Loop through command nodes
  2884.   for (var i = 0; i < commandset.childNodes.length; i++)
  2885.   {
  2886.     var commandID = commandset.childNodes[i].getAttribute("id");
  2887.     if (commandID)
  2888.     {
  2889.       if (commandID == "cmd_InsertTable" ||
  2890.           commandID == "cmd_JoinTableCells" ||
  2891.           commandID == "cmd_SplitTableCell" ||
  2892.           commandID == "cmd_ConvertToTable")
  2893.       {
  2894.         // Call the update method in the command class
  2895.         goUpdateCommand(commandID);
  2896.       }
  2897.       // Directly set with the values calculated here
  2898.       else if (commandID == "cmd_DeleteTable" ||
  2899.                commandID == "cmd_NormalizeTable" ||
  2900.                commandID == "cmd_editTable" ||
  2901.                commandID == "cmd_TableOrCellColor" ||
  2902.                commandID == "cmd_SelectTable")
  2903.       {
  2904.         goSetCommandEnabled(commandID, enabledIfTable);
  2905.       } else {
  2906.         goSetCommandEnabled(commandID, enabled);
  2907.       }
  2908.     }
  2909.   }
  2910. }
  2911.  
  2912. //-----------------------------------------------------------------------------------
  2913. // Helpers for inserting and editing tables:
  2914.  
  2915. function IsInTable()
  2916. {
  2917.   var editor = GetCurrentEditor();
  2918.   try {
  2919.     var flags = editor.flags;
  2920.     return (IsHTMLEditor() &&
  2921.             !(flags & nsIPlaintextEditor.eEditorReadonlyMask) &&
  2922.             IsEditingRenderedHTML() &&
  2923.             null != editor.getElementOrParentByTagName("table", null));
  2924.   } catch (e) {}
  2925.   return false;
  2926. }
  2927.  
  2928. function IsInTableCell()
  2929. {
  2930.   try {
  2931.     var editor = GetCurrentEditor();
  2932.     var flags = editor.flags;
  2933.     return (IsHTMLEditor() &&
  2934.             !(flags & nsIPlaintextEditor.eEditorReadonlyMask) && 
  2935.             IsEditingRenderedHTML() &&
  2936.             null != editor.getElementOrParentByTagName("td", null));
  2937.   } catch (e) {}
  2938.   return false;
  2939.  
  2940. }
  2941.  
  2942. function IsSelectionInOneCell()
  2943. {
  2944.   try {
  2945.     var editor = GetCurrentEditor();
  2946.     var selection = editor.selection;
  2947.  
  2948.     if (selection.rangeCount == 1)
  2949.     {
  2950.       // We have a "normal" single-range selection
  2951.       if (!selection.isCollapsed &&
  2952.          selection.anchorNode != selection.focusNode)
  2953.       {
  2954.         // Check if both nodes are within the same cell
  2955.         var anchorCell = editor.getElementOrParentByTagName("td", selection.anchorNode);
  2956.         var focusCell = editor.getElementOrParentByTagName("td", selection.focusNode);
  2957.         return (focusCell != null && anchorCell != null && (focusCell == anchorCell));
  2958.       }
  2959.       // Collapsed selection or anchor == focus (thus must be in 1 cell)
  2960.       return true;
  2961.     }
  2962.   } catch (e) {}
  2963.   return false;
  2964. }
  2965.  
  2966. // Call this with insertAllowed = true to allow inserting if not in existing table,
  2967. //   else use false to do nothing if not in a table
  2968. function EditorInsertOrEditTable(insertAllowed)
  2969. {
  2970.   if (IsInTable())
  2971.   {
  2972.     // Edit properties of existing table
  2973.     window.openDialog("chrome://editor/content/EdTableProps.xul", "_blank", "chrome,close,titlebar,modal", "","TablePanel");
  2974.     gContentWindow.focus();
  2975.   } 
  2976.   else if (insertAllowed)
  2977.   {
  2978.     try {
  2979.       if (GetCurrentEditor().selection.isCollapsed)
  2980.         // If we have a caret, insert a blank table...
  2981.         EditorInsertTable();
  2982.       else
  2983.         // else convert the selection into a table
  2984.         goDoCommand("cmd_ConvertToTable");
  2985.     } catch (e) {}
  2986.   }
  2987. }
  2988.  
  2989. function EditorInsertTable()
  2990. {
  2991.   // Insert a new table
  2992.   window.openDialog("chrome://editor/content/EdInsertTable.xul", "_blank", "chrome,close,titlebar,modal", "");
  2993.   gContentWindow.focus();
  2994. }
  2995.  
  2996. function EditorTableCellProperties()
  2997. {
  2998.   if (!IsHTMLEditor())
  2999.     return;
  3000.  
  3001.   try {
  3002.     var cell = GetCurrentEditor().getElementOrParentByTagName("td", null);
  3003.     if (cell) {
  3004.       // Start Table Properties dialog on the "Cell" panel
  3005.       window.openDialog("chrome://editor/content/EdTableProps.xul", "_blank", "chrome,close,titlebar,modal", "", "CellPanel");
  3006.       gContentWindow.focus();
  3007.     }
  3008.   } catch (e) {}
  3009. }
  3010.  
  3011. function GetNumberOfContiguousSelectedRows()
  3012. {
  3013.   if (!IsHTMLEditor())
  3014.     return 0;
  3015.  
  3016.   var rows = 0;
  3017.   try {
  3018.     var editor = GetCurrentTableEditor();
  3019.     var rowObj = { value: 0 };
  3020.     var colObj = { value: 0 };
  3021.     var cell = editor.getFirstSelectedCellInTable(rowObj, colObj);
  3022.     if (!cell)
  3023.       return 0;
  3024.  
  3025.     // We have at least one row
  3026.     rows++;
  3027.  
  3028.     var lastIndex = rowObj.value;
  3029.     do {
  3030.       cell = editor.getNextSelectedCell({value:0});
  3031.       if (cell)
  3032.       {
  3033.         editor.getCellIndexes(cell, rowObj, colObj);
  3034.         var index = rowObj.value;
  3035.         if (index == lastIndex + 1)
  3036.         {
  3037.           lastIndex = index;
  3038.           rows++;
  3039.         }
  3040.       }
  3041.     }
  3042.     while (cell);
  3043.   } catch (e) {}
  3044.  
  3045.   return rows;
  3046. }
  3047.  
  3048. function GetNumberOfContiguousSelectedColumns()
  3049. {
  3050.   if (!IsHTMLEditor())
  3051.     return 0;
  3052.  
  3053.   var columns = 0;
  3054.   try {
  3055.     var editor = GetCurrentTableEditor();
  3056.     var colObj = { value: 0 };
  3057.     var rowObj = { value: 0 };
  3058.     var cell = editor.getFirstSelectedCellInTable(rowObj, colObj);
  3059.     if (!cell)
  3060.       return 0;
  3061.  
  3062.     // We have at least one column
  3063.     columns++;
  3064.  
  3065.     var lastIndex = colObj.value;
  3066.     do {
  3067.       cell = editor.getNextSelectedCell({value:0});
  3068.       if (cell)
  3069.       {
  3070.         editor.getCellIndexes(cell, rowObj, colObj);
  3071.         var index = colObj.value;
  3072.         if (index == lastIndex +1)
  3073.         {
  3074.           lastIndex = index;
  3075.           columns++;
  3076.         }
  3077.       }
  3078.     }
  3079.     while (cell);
  3080.   } catch (e) {}
  3081.  
  3082.   return columns;
  3083. }
  3084.  
  3085. function EditorOnFocus()
  3086. {
  3087.   // Current window already has the InsertCharWindow
  3088.   if ("InsertCharWindow" in window && window.InsertCharWindow) return;
  3089.  
  3090.   // Find window with an InsertCharsWindow and switch association to this one
  3091.   var windowWithDialog = FindEditorWithInsertCharDialog();
  3092.   if (windowWithDialog)
  3093.   {
  3094.     // Switch the dialog to current window
  3095.     // this sets focus to dialog, so bring focus back to editor window
  3096.     if (SwitchInsertCharToThisWindow(windowWithDialog))
  3097.       top.document.commandDispatcher.focusedWindow.focus();
  3098.   }
  3099. }
  3100.  
  3101. function SwitchInsertCharToThisWindow(windowWithDialog)
  3102. {
  3103.   if (windowWithDialog && "InsertCharWindow" in windowWithDialog &&
  3104.       windowWithDialog.InsertCharWindow)
  3105.   {
  3106.     // Move dialog association to the current window
  3107.     window.InsertCharWindow = windowWithDialog.InsertCharWindow;
  3108.     windowWithDialog.InsertCharWindow = null;
  3109.  
  3110.     // Switch the dialog's opener to current window's
  3111.     window.InsertCharWindow.opener = window;
  3112.  
  3113.     // Bring dialog to the forground
  3114.     window.InsertCharWindow.focus();
  3115.     return true;
  3116.   }
  3117.   return false;
  3118. }
  3119.  
  3120. function FindEditorWithInsertCharDialog()
  3121. {
  3122.   try {
  3123.     // Find window with an InsertCharsWindow and switch association to this one
  3124.     var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1'].getService();
  3125.     var windowManagerInterface = windowManager.QueryInterface( Components.interfaces.nsIWindowMediator);
  3126.     var enumerator = windowManagerInterface.getEnumerator( null );
  3127.  
  3128.     while ( enumerator.hasMoreElements()  )
  3129.     {
  3130.       var tempWindow = enumerator.getNext();
  3131.  
  3132.       if (tempWindow != window && "InsertCharWindow" in tempWindow &&
  3133.           tempWindow.InsertCharWindow)
  3134.       {
  3135.         return tempWindow;
  3136.       }
  3137.     }
  3138.   }
  3139.   catch(e) {}
  3140.   return null;
  3141. }
  3142.  
  3143. function EditorFindOrCreateInsertCharWindow()
  3144. {
  3145.   if ("InsertCharWindow" in window && window.InsertCharWindow)
  3146.     window.InsertCharWindow.focus();
  3147.   else
  3148.   {
  3149.     // Since we switch the dialog during EditorOnFocus(),
  3150.     //   this should really never be found, but it's good to be sure
  3151.     var windowWithDialog = FindEditorWithInsertCharDialog();
  3152.     if (windowWithDialog)
  3153.     {
  3154.       SwitchInsertCharToThisWindow(windowWithDialog);
  3155.     }
  3156.     else
  3157.     {
  3158.       // The dialog will set window.InsertCharWindow to itself
  3159.       window.openDialog("chrome://editor/content/EdInsertChars.xul", "_blank", "chrome,close,titlebar", "");
  3160.     }
  3161.   }
  3162. }
  3163.  
  3164. // Find another HTML editor window to associate with the InsertChar dialog
  3165. //   or close it if none found  (May be a mail composer)
  3166. function SwitchInsertCharToAnotherEditorOrClose()
  3167. {
  3168.   if ("InsertCharWindow" in window && window.InsertCharWindow)
  3169.   {
  3170.     var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1'].getService();
  3171.     var enumerator;
  3172.     try {
  3173.       var windowManagerInterface = windowManager.QueryInterface( Components.interfaces.nsIWindowMediator);
  3174.       enumerator = windowManagerInterface.getEnumerator( null );
  3175.     }
  3176.     catch(e) {}
  3177.     if (!enumerator) return;
  3178.  
  3179.     // TODO: Fix this to search for command controllers and look for "cmd_InsertChars"
  3180.     // For now, detect just Web Composer and HTML Mail Composer
  3181.     while ( enumerator.hasMoreElements()  )
  3182.     {
  3183.       var  tempWindow = enumerator.getNext();
  3184.       if (tempWindow != window && tempWindow != window.InsertCharWindow &&
  3185.           "GetCurrentEditor" in tempWindow && tmpWindow.GetCurrentEditor())
  3186.       {
  3187.         tempWindow.InsertCharWindow = window.InsertCharWindow;
  3188.         window.InsertCharWindow = null;
  3189.         tempWindow.InsertCharWindow.opener = tempWindow;
  3190.         return;
  3191.       }
  3192.     }
  3193.     // Didn't find another editor - close the dialog
  3194.     window.InsertCharWindow.close();
  3195.   }
  3196. }
  3197.  
  3198. function ResetStructToolbar()
  3199. {
  3200.   gLastFocusNode = null;
  3201.   UpdateStructToolbar();
  3202. }
  3203.  
  3204. function newCommandListener(element)
  3205. {
  3206.   return function() { return SelectFocusNodeAncestor(element); };
  3207. }
  3208.  
  3209. function newContextmenuListener(button, element)
  3210. {
  3211.   return function() { return InitStructBarContextMenu(button, element); };
  3212. }
  3213.  
  3214. function UpdateStructToolbar()
  3215. {
  3216.   var editor = GetCurrentEditor();
  3217.   if (!editor) return;
  3218.  
  3219.   var mixed = GetSelectionContainer();
  3220.   if (!mixed) return;
  3221.   var element = mixed.node;
  3222.   var oneElementSelected = mixed.oneElementSelected;
  3223.  
  3224.   if (!element) return;
  3225.  
  3226.   if (element == gLastFocusNode &&
  3227.       oneElementSelected == gLastFocusNodeWasSelected)
  3228.     return;
  3229.  
  3230.   gLastFocusNode = element;
  3231.   gLastFocusNodeWasSelected = mixed.oneElementSelected;
  3232.  
  3233.   var toolbar = document.getElementById("structToolbar");
  3234.   if (!toolbar) return;
  3235.   var childNodes = toolbar.childNodes;
  3236.   var childNodesLength = childNodes.length;
  3237.   // We need to leave the <label> to flex the buttons to the left
  3238.   // so, don't remove the last child at position length - 1
  3239.   for (var i = childNodesLength - 2; i >= 0; i--) {
  3240.     toolbar.removeChild(childNodes.item(i));
  3241.   }
  3242.  
  3243.   toolbar.removeAttribute("label");
  3244.  
  3245.   if ( IsInHTMLSourceMode() ) {
  3246.     // we have destroyed the contents of the status bar and are
  3247.     // about to recreate it ; but we don't want to do that in
  3248.     // Source mode
  3249.     return;
  3250.   }
  3251.  
  3252.   var tag, button;
  3253.   var bodyElement = GetBodyElement();
  3254.   var isFocusNode = true;
  3255.   var tmp;
  3256.   do {
  3257.     tag = element.nodeName.toLowerCase();
  3258.  
  3259.     button = document.createElementNS(XUL_NS, "toolbarbutton");
  3260.     button.setAttribute("label",   "<" + tag + ">");
  3261.     button.setAttribute("value",   tag);
  3262.     button.setAttribute("context", "structToolbarContext");
  3263.     button.className = "struct-button";
  3264.  
  3265.     toolbar.insertBefore(button, toolbar.firstChild);
  3266.  
  3267.     button.addEventListener("command", newCommandListener(element), false);
  3268.  
  3269.     button.addEventListener("contextmenu", newContextmenuListener(button, element), false);
  3270.  
  3271.     if (isFocusNode && oneElementSelected) {
  3272.       button.setAttribute("checked", "true");
  3273.       isFocusNode = false;
  3274.     }
  3275.  
  3276.     tmp = element;
  3277.     element = element.parentNode;
  3278.  
  3279.   } while (tmp != bodyElement);
  3280. }
  3281.  
  3282. function SelectFocusNodeAncestor(element)
  3283. {
  3284.   var editor = GetCurrentEditor();
  3285.   if (editor) {
  3286.     if (element == GetBodyElement())
  3287.       editor.selectAll();
  3288.     else
  3289.       editor.selectElement(element);
  3290.   }
  3291.   ResetStructToolbar();
  3292. }
  3293.  
  3294. function GetSelectionContainer()
  3295. {
  3296.   var editor = GetCurrentEditor();
  3297.   if (!editor) return null;
  3298.  
  3299.   try {
  3300.     var selection = editor.selection;
  3301.     if (!selection) return null;
  3302.   }
  3303.   catch (e) { return null; }
  3304.  
  3305.   var result = { oneElementSelected:false };
  3306.  
  3307.   if (selection.isCollapsed) {
  3308.     result.node = selection.focusNode;
  3309.   }
  3310.   else {
  3311.     var rangeCount = selection.rangeCount;
  3312.     if (rangeCount == 1) {
  3313.       result.node = editor.getSelectedElement("");
  3314.       var range = selection.getRangeAt(0);
  3315.  
  3316.       // check for a weird case : when we select a piece of text inside
  3317.       // a text node and apply an inline style to it, the selection starts
  3318.       // at the end of the text node preceding the style and ends after the
  3319.       // last char of the style. Assume the style element is selected for
  3320.       // user's pleasure
  3321.       if (!result.node &&
  3322.           range.startContainer.nodeType == Node.TEXT_NODE &&
  3323.           range.startOffset == range.startContainer.length &&
  3324.           range.endContainer.nodeType == Node.TEXT_NODE &&
  3325.           range.endOffset == range.endContainer.length &&
  3326.           range.endContainer.nextSibling == null &&
  3327.           range.startContainer.nextSibling == range.endContainer.parentNode)
  3328.         result.node = range.endContainer.parentNode;
  3329.  
  3330.       if (!result.node) {
  3331.         // let's rely on the common ancestor of the selection
  3332.         result.node = range.commonAncestorContainer;
  3333.       }
  3334.       else {
  3335.         result.oneElementSelected = true;
  3336.       }
  3337.     }
  3338.     else {
  3339.       // assume table cells !
  3340.       var i, container = null;
  3341.       for (i = 0; i < rangeCount; i++) {
  3342.         range = selection.getRangeAt(i);
  3343.         if (!container) {
  3344.           container = range.startContainer;
  3345.         }
  3346.         else if (container != range.startContainer) {
  3347.           // all table cells don't belong to same row so let's
  3348.           // select the parent of all rows
  3349.           result.node = container.parentNode;
  3350.           break;
  3351.         }
  3352.         result.node = container;
  3353.       }
  3354.     }
  3355.   }
  3356.  
  3357.   // make sure we have an element here
  3358.   while (result.node.nodeType != Node.ELEMENT_NODE)
  3359.     result.node = result.node.parentNode;
  3360.  
  3361.   // and make sure the element is not a special editor node like
  3362.   // the <br> we insert in blank lines
  3363.   // and don't select anonymous content !!! (fix for bug 190279)
  3364.   while (result.node.hasAttribute("_moz_editor_bogus_node") ||
  3365.          editor.isAnonymousElement(result.node))
  3366.     result.node = result.node.parentNode;
  3367.  
  3368.   return result;
  3369. }
  3370.  
  3371. function FillInHTMLTooltip(tooltip)
  3372. {
  3373.   const XLinkNS = "http://www.w3.org/1999/xlink";
  3374.   var tooltipText = null;
  3375.   if (gEditorDisplayMode == kDisplayModePreview) {
  3376.     for (var node = document.tooltipNode; node; node = node.parentNode) {
  3377.       if (node.nodeType == Node.ELEMENT_NODE) {
  3378.         tooltipText = node.getAttributeNS(XLinkNS, "title");
  3379.         if (tooltipText && /\S/.test(tooltipText)) {
  3380.           tooltip.setAttribute("label", tooltipText);
  3381.           return true;
  3382.         }
  3383.         tooltipText = node.getAttribute("title");
  3384.         if (tooltipText && /\S/.test(tooltipText)) {
  3385.           tooltip.setAttribute("label", tooltipText);
  3386.           return true;
  3387.         }
  3388.       }
  3389.     }
  3390.   } else {
  3391.     for (var node = document.tooltipNode; node; node = node.parentNode) {
  3392.       if (node instanceof Components.interfaces.nsIDOMHTMLImageElement ||
  3393.           node instanceof Components.interfaces.nsIDOMHTMLInputElement)
  3394.         tooltipText = node.getAttribute("src");
  3395.       else if (node instanceof Components.interfaces.nsIDOMHTMLAnchorElement)
  3396.         tooltipText = node.getAttribute("href") || node.name;
  3397.       if (tooltipText) {
  3398.         tooltip.setAttribute("label", tooltipText);
  3399.         return true;
  3400.       }
  3401.     }
  3402.   }
  3403.   return false;
  3404. }
  3405.  
  3406. function UpdateTOC()
  3407. {
  3408.   window.openDialog("chrome://editor/content/EdInsertTOC.xul",
  3409.                     "_blank", "chrome,close,modal,titlebar");
  3410.   window._content.focus();
  3411. }
  3412.  
  3413. function InitTOCMenu()
  3414. {
  3415.   var elt = GetCurrentEditor().document.getElementById("mozToc");
  3416.   var createMenuitem = document.getElementById("insertTOCMenuitem");
  3417.   var updateMenuitem = document.getElementById("updateTOCMenuitem");
  3418.   var removeMenuitem = document.getElementById("removeTOCMenuitem");
  3419.   if (removeMenuitem && createMenuitem && updateMenuitem) {
  3420.     if (elt) {
  3421.       createMenuitem.setAttribute("disabled", "true");
  3422.       updateMenuitem.removeAttribute("disabled");
  3423.       removeMenuitem.removeAttribute("disabled");
  3424.     }
  3425.     else {
  3426.       createMenuitem.removeAttribute("disabled");
  3427.       removeMenuitem.setAttribute("disabled", "true");
  3428.       updateMenuitem.setAttribute("disabled", "true");
  3429.     }
  3430.   }
  3431. }
  3432.  
  3433. function RemoveTOC()
  3434. {
  3435.   var theDocument = GetCurrentEditor().document;
  3436.   var elt = theDocument.getElementById("mozToc");
  3437.   if (elt) {
  3438.     elt.parentNode.removeChild(elt);
  3439.   }
  3440.  
  3441.   function acceptNode(node)
  3442.   {
  3443.     if (node.nodeName.toLowerCase() == "a" &&
  3444.         node.hasAttribute("name") &&
  3445.         node.getAttribute("name").substr(0, 8) == "mozTocId") {
  3446.       return NodeFilter.FILTER_ACCEPT;
  3447.     }
  3448.     return NodeFilter.FILTER_SKIP;
  3449.   }
  3450.  
  3451.   var treeWalker = theDocument.createTreeWalker(theDocument.documentElement,
  3452.                                                 NodeFilter.SHOW_ELEMENT,
  3453.                                                 acceptNode,
  3454.                                                 true);
  3455.   if (treeWalker) {
  3456.     var anchorNode = treeWalker.nextNode();
  3457.     while (anchorNode) {
  3458.       var tmp = treeWalker.nextNode();
  3459.       anchorNode.parentNode.removeChild(anchorNode);
  3460.       anchorNode = tmp;
  3461.     }
  3462.   }
  3463. }
  3464.