Bug 1556203 - Fix context menu for chat by hand-rolling it, also use correct spellcheck dictionary. r=aceman
authorJorg K <jorgk@jorgk.com>
Mon, 17 Jun 2019 00:20:23 +0200
changeset 35876 f72f5ad61b913c02d8eae1c700cfcf5d964cd3eb
parent 35875 0a5868fe98d9ad6bec9922ae6d5d0e1a33203227
child 35877 686ff7f6f966a57217bef39351503db397669c36
push id392
push userclokep@gmail.com
push dateMon, 02 Sep 2019 20:17:19 +0000
reviewersaceman
bugs1556203
Bug 1556203 - Fix context menu for chat by hand-rolling it, also use correct spellcheck dictionary. r=aceman
mail/base/content/messenger.xul
mail/components/im/content/chat-menu.inc.xul
mail/components/im/content/chat-messenger.js
mail/components/im/content/imconversation.xml
--- a/mail/base/content/messenger.xul
+++ b/mail/base/content/messenger.xul
@@ -52,16 +52,18 @@
 <!ENTITY % charsetDTD SYSTEM "chrome://global/locale/charsetMenu.dtd">
 %charsetDTD;
 <!ENTITY % viewZoomOverlayDTD SYSTEM "chrome://messenger/locale/viewZoomOverlay.dtd">
 %viewZoomOverlayDTD;
 <!ENTITY % msgReadSMIMEDTD SYSTEM "chrome://messenger-smime/locale/msgReadSMIMEOverlay.dtd">
 %msgReadSMIMEDTD;
 <!ENTITY % msgViewPickerDTD SYSTEM "chrome://messenger/locale/msgViewPickerOverlay.dtd" >
 %msgViewPickerDTD;
+<!-- for the chat context menu -->
+<!ENTITY % textcontextDTD SYSTEM "chrome://global/locale/textcontext.dtd">
 ]>
 
 <!--
   - The 'what you think of when you think of thunderbird' window;
   -  3-pane view inside of tabs.
   -->
 <window id="messengerWindow"
         xmlns:html="http://www.w3.org/1999/xhtml"
--- a/mail/components/im/content/chat-menu.inc.xul
+++ b/mail/components/im/content/chat-menu.inc.xul
@@ -55,8 +55,53 @@
   </menupopup>
 
   <menupopup id="chat-toolbar-context-menu">
     <menuitem id="CustomizeChatToolbar"
               oncommand="CustomizeMailToolbar('chat-view-toolbox', 'CustomizeChatToolbar')"
               label="&customizeToolbar.label;"
               accesskey="&customizeToolbar.accesskey;"/>
   </menupopup>
+
+  <menupopup id="chatContextMenu"
+             onpopupshowing="if (event.target != this) return true; openChatContextMenu(this);"
+             onpopuphiding="if (event.target == this) { clearChatContextMenu(this); }">
+
+    <!-- Spellchecking menu items -->
+    <menuitem id="spellCheckNoSuggestions"
+              label="&spellNoSuggestions.label;"
+              disabled="true"/>
+    <menuseparator id="spellCheckAddSep" />
+    <menuitem id="spellCheckAddToDictionary"
+              label="&spellAddToDictionary.label;"
+              accesskey="&spellAddToDictionary.accesskey;"
+              oncommand="gChatSpellChecker.addToDictionary();"/>
+    <menuseparator id="spellCheckSuggestionsSeparator"/>
+
+    <menuitem label="&undoCmd.label;" accesskey="&undoCmd.accesskey;" command="cmd_undo"/>
+    <menuitem label="&cutCmd.label;" accesskey="&cutCmd.accesskey;" command="cmd_cut"/>
+    <menuitem label="&copyCmd.label;" accesskey="&copyCmd.accesskey;" command="cmd_copy"/>
+    <menuitem label="&pasteCmd.label;" accesskey="&pasteCmd.accesskey;" command="cmd_paste"/>
+    <menuseparator/>
+    <menuitem label="&selectAllCmd.label;"
+              accesskey="&selectAllCmd.accesskey;"
+              command="cmd_selectAll"/>
+
+    <!-- Spellchecking general menu items (enable, add dictionaries...) -->
+    <menuseparator id="spellCheckSeparator"/>
+    <menuitem id="spellCheckEnable"
+              label="&spellCheckToggle.label;"
+              type="checkbox"
+              accesskey="&spellCheckToggle.accesskey;"
+              oncommand="enableInlineSpellCheck(!gChatSpellChecker.enabled);"/>
+    <menu id="spellCheckDictionaries"
+          label="&spellDictionaries.label;"
+          accesskey="&spellDictionaries.accesskey;">
+      <menupopup id="spellCheckDictionariesMenu">
+        <menuseparator id="spellCheckLanguageSeparator"/>
+        <menuitem id="spellCheckAddDictionaries"
+                  label="&spellAddDictionaries.label;"
+                  accesskey="&spellAddDictionaries.accesskey;"
+                  oncommand="openDictionaryList();"/>
+      </menupopup>
+    </menu>
+
+  </menupopup>
--- a/mail/components/im/content/chat-messenger.js
+++ b/mail/components/im/content/chat-messenger.js
@@ -1,28 +1,85 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* global MozElements */
+/* import-globals-from ../../../../../toolkit/content/globalOverlay.js */
 
 // This file is loaded in messenger.xul.
 /* globals fixIterator, MailToolboxCustomizeDone, Notifications, openIMAccountMgr,
    PROTO_TREE_VIEW, Services, Status, statusSelector, ZoomManager */
 
 var {Notifications} = ChromeUtils.import("resource:///modules/chatNotifications.jsm");
 var { Services: imServices } = ChromeUtils.import("resource:///modules/imServices.jsm");
 var {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+var {InlineSpellChecker} = ChromeUtils.import("resource://gre/modules/InlineSpellChecker.jsm");
 
 ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 ChromeUtils.defineModuleGetter(this, "OTRUI", "resource:///modules/OTRUI.jsm");
 
+var gChatSpellChecker;
+var gRangeParent;
+var gRangeOffset;
+
 var gOtrEnabled = false;
 var gBuddyListContextMenu = null;
 
+function openChatContextMenu(popup) {
+  let conv = chatHandler._getActiveConvView();
+  let spellchecker = conv.spellchecker;
+  let textbox = conv.editor;
+
+  // The context menu uses gChatSpellChecker, so set it here for the duration of the menu.
+  gChatSpellChecker = spellchecker;
+
+  spellchecker.init(textbox.editor);
+  spellchecker.initFromEvent(gRangeParent, gRangeOffset);
+  let onMisspelling = spellchecker.overMisspelling;
+  document.getElementById("spellCheckSuggestionsSeparator").hidden = !onMisspelling;
+  document.getElementById("spellCheckAddToDictionary").hidden = !onMisspelling;
+  let separator = document.getElementById("spellCheckAddSep");
+  separator.hidden = !onMisspelling;
+  document.getElementById("spellCheckNoSuggestions").hidden = !onMisspelling ||
+      spellchecker.addSuggestionsToMenu(popup, separator, 5);
+
+  let dictMenu = document.getElementById("spellCheckDictionariesMenu");
+  let dictSep = document.getElementById("spellCheckLanguageSeparator");
+  spellchecker.addDictionaryListToMenu(dictMenu, dictSep);
+
+  document.getElementById("spellCheckEnable")
+          .setAttribute("checked", spellchecker.enabled);
+  document.getElementById("spellCheckDictionaries")
+          .setAttribute("hidden", !spellchecker.enabled);
+
+  goUpdateCommand("cmd_undo");
+  goUpdateCommand("cmd_copy");
+  goUpdateCommand("cmd_cut");
+  goUpdateCommand("cmd_paste");
+  goUpdateCommand("cmd_selectAll");
+}
+
+function clearChatContextMenu(popup) {
+  let conv = chatHandler._getActiveConvView();
+  let spellchecker = conv.spellchecker;
+  spellchecker.clearDictionaryListFromMenu();
+  spellchecker.clearSuggestionsFromMenu();
+}
+
+// This function modifies gChatSpellChecker and updates the UI accordingly. It's
+// called when the user clicks on context menu to toggle the spellcheck feature.
+function enableInlineSpellCheck(aEnableInlineSpellCheck) {
+  gChatSpellChecker.enabled = aEnableInlineSpellCheck;
+  document.getElementById("spellCheckEnable")
+          .setAttribute("checked", aEnableInlineSpellCheck);
+  document.getElementById("spellCheckDictionaries")
+          .setAttribute("hidden", !aEnableInlineSpellCheck);
+}
+
 function buddyListContextMenu(aXulMenu) {
   this.target = aXulMenu.triggerNode;
   this.menu = aXulMenu;
   let localName = this.target.localName;
   this.onContact = (localName == "richlistitem" &&
     this.target.getAttribute("is") == "chat-contact");
   this.onConv = (localName == "richlistitem" &&
     this.target.getAttribute("is") == "chat-imconv");
@@ -680,16 +737,24 @@ var chatHandler = {
         convDeck.appendChild(conv);
         conv.conv = item.conv;
         conv.tab = item;
         conv.setAttribute("contentcontextmenu", "chatConversationContextMenu");
         conv.setAttribute("contenttooltip", "imTooltip");
         item.convView = conv;
         document.getElementById("contextSplitter").hidden = false;
         document.getElementById("contextPane").hidden = false;
+        conv.addEventListener("contextmenu", (e) => {
+          // Stash away the original event's parent and range for later use.
+          gRangeParent = e.rangeParent;
+          gRangeOffset = e.rangeOffset;
+          let popup = document.getElementById("chatContextMenu");
+          popup.openPopupAtScreen(e.screenX, e.screenY, true);
+          e.preventDefault();
+        });
       } else {
         item.convView.onConvResize();
       }
 
       convDeck.selectedPanel = item.convView;
       item.convView.updateConvStatus();
       item.update();
 
--- a/mail/components/im/content/imconversation.xml
+++ b/mail/components/im/content/imconversation.xml
@@ -63,18 +63,20 @@
        browser.addEventListener("keypress", this.browserKeyPress.bind(this));
        browser.addEventListener("dblclick", this.browserDblClick.bind(this));
        Services.obs.addObserver(this, "conversation-loaded");
 
        // @implements {nsIObserver}
        this.prefObserver = (subject, topic, data) => {
          if (Services.prefs.getBoolPref("mail.spellcheck.inline")) {
            this.editor.setAttribute("spellcheck", "true");
+           this._spellchecker.enabled = true;
          } else {
            this.editor.removeAttribute("spellcheck");
+           this._spellchecker.enabled = false;
          }
        };
        Services.prefs.addObserver("mail.spellcheck.inline", this.prefObserver);
       ]]>
      </constructor>
 
      <destructor>
       <![CDATA[
@@ -406,28 +408,32 @@
      </method>
 
      <method name="initTextboxFormat">
       <body>
       <![CDATA[
         let textbox = this.editor;
 
         const {MessageFormat} = ChromeUtils.import("resource:///modules/imTextboxUtils.jsm");
+        const {InlineSpellChecker} = ChromeUtils.import("resource://gre/modules/InlineSpellChecker.jsm");
         MessageFormat.registerTextbox(textbox);
 
         // Init the textbox size
         this.calculateTextboxDefaultHeight();
         textbox.parentNode.height = textbox.defaultHeight +
                                     this._TEXTBOX_VERTICAL_OVERHEAD;
         textbox.style.overflowY = "hidden";
 
+        this._spellchecker = new InlineSpellChecker(textbox.editor);
         if (Services.prefs.getBoolPref("mail.spellcheck.inline")) {
           textbox.setAttribute("spellcheck", "true");
+          this._spellchecker.enabled = true;
         } else {
           textbox.removeAttribute("spellcheck");
+          this._spellchecker.enabled = false;
         }
       ]]>
       </body>
      </method>
 
      <method name="inputKeyPress">
       <parameter name="event"/>
       <body>
@@ -1676,16 +1682,17 @@
          <![CDATA[
           if (!this._editor)
             this._editor = this.getElt("inputBox");
           return this._editor;
          ]]>
        </getter>
      </property>
 
+     <property name="spellchecker" onget="return this._spellchecker;"/>
      <property name="browser" onget="return this.getElt('browser');"/>
      <property name="contentWindow" onget="return this.browser.contentWindow;"/>
      <property name="findbar" onget="return this.getElt('FindToolbar');"/>
 
      <property name="bundle">
        <getter>
          <![CDATA[
           if (!this._bundle) {