Bug 398928 - Allow moving a window by dragging toolbar or statusbar, r=enndeakin
☠☠ backed out by d8a6df699a26 ☠ ☠
authorMarkus Stange <mstange@themasta.com>
Mon, 13 Oct 2008 11:24:51 +0200
changeset 20365 151e51ec625e910335112572d678bdc9fecd6ad8
parent 20364 07c7fa8394c53ae773dc78050cdb45fa4e973c1f
child 20366 aff392727ff43bd7f71ed450047eb6fd59f09bd4
child 20370 d8a6df699a2635c9bf3c02a97941a0a540844200
push idunknown
push userunknown
push dateunknown
reviewersenndeakin
bugs398928
milestone1.9.1b2pre
Bug 398928 - Allow moving a window by dragging toolbar or statusbar, r=enndeakin
browser/base/content/browser.xul
browser/base/content/pageinfo/pageInfo.xul
toolkit/components/console/content/console.xul
toolkit/content/Makefile.in
toolkit/content/WindowDraggingUtils.jsm
toolkit/content/customizeToolbar.js
toolkit/content/widgets/general.xml
toolkit/content/widgets/preferences.xml
toolkit/content/widgets/toolbar.xml
toolkit/mozapps/downloads/content/downloads.xul
toolkit/mozapps/extensions/content/extensions.xul
toolkit/themes/pinstripe/global/global.css
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -411,17 +411,18 @@
         <searchbar id="searchbar" flex="1" chromedir="&locale.dir;"
                    newlines="replacewithspaces"/>
       </toolbaritem>
 
       <toolbarbutton id="print-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
                      label="&printButton.label;" command="cmd_print"
                      tooltiptext="&printButton.tooltip;"/>
 
-      <toolbaritem id="throbber-box" title="&throbberItem.title;" align="center" pack="center">
+      <toolbaritem id="throbber-box" title="&throbberItem.title;" align="center" pack="center"
+                   mousethrough="always">
         <button id="navigator-throbber" disabled="true"/>
       </toolbaritem>
 
       <toolbaritem flex="1" id="personal-bookmarks" title="&bookmarksItem.title;">
          <hbox id="bookmarksBarContent" flex="1"
                type="places"
                place="place:folder=TOOLBAR"
                context="placesContext"
--- a/browser/base/content/pageinfo/pageInfo.xul
+++ b/browser/base/content/pageinfo/pageInfo.xul
@@ -107,31 +107,33 @@
     <key key="&selectall.key;"   modifiers="alt"   command="cmd_selectall"/>
   </keyset>
 
   <menupopup id="picontext">
     <menuitem id="menu_selectall" label="&selectall.label;" command="cmd_selectall" accesskey="&selectall.accesskey;"/>
     <menuitem id="menu_copy"      label="&copy.label;"      command="cmd_copy"      accesskey="&copy.accesskey;"/>
   </menupopup>
 
-  <stack id="topStackBar">
-    <radiogroup id="viewGroup" class="viewSelector chromeclass-toolbar" orient="horizontal">
-      <radio id="generalTab"  label="&generalTab;"  accesskey="&generalTab.accesskey;"
-           oncommand="showTab('general');"/>
-      <radio id="mediaTab"    label="&mediaTab;"    accesskey="&mediaTab.accesskey;"
-           oncommand="showTab('media'); ensureSelection(gImageView)" hidden="true"/>
-      <radio id="feedTab"     label="&feedTab;"     accesskey="&feedTab.accesskey;"
-           oncommand="showTab('feed');" hidden="true"/>
-      <radio id="permTab"     label="&permTab;"     accesskey="&permTab.accesskey;"
-           oncommand="showTab('perm');"/>
-      <radio id="securityTab" label="&securityTab;" accesskey="&securityTab.accesskey;"
-           oncommand="showTab('security');"/>
-      <!-- Others added by overlay -->
-    </radiogroup>
-  </stack>
+  <windowdragbox orient="vertical">
+    <stack id="topStackBar">
+      <radiogroup id="viewGroup" class="viewSelector chromeclass-toolbar" orient="horizontal">
+        <radio id="generalTab"  label="&generalTab;"  accesskey="&generalTab.accesskey;"
+             oncommand="showTab('general');"/>
+        <radio id="mediaTab"    label="&mediaTab;"    accesskey="&mediaTab.accesskey;"
+             oncommand="showTab('media'); ensureSelection(gImageView)" hidden="true"/>
+        <radio id="feedTab"     label="&feedTab;"     accesskey="&feedTab.accesskey;"
+             oncommand="showTab('feed');" hidden="true"/>
+        <radio id="permTab"     label="&permTab;"     accesskey="&permTab.accesskey;"
+             oncommand="showTab('perm');"/>
+        <radio id="securityTab" label="&securityTab;" accesskey="&securityTab.accesskey;"
+             oncommand="showTab('security');"/>
+        <!-- Others added by overlay -->
+      </radiogroup>
+    </stack>
+  </windowdragbox>
 
   <deck id="mainDeck" flex="1">
     <!-- General page information -->
     <vbox id="generalPanel">
       <textbox class="header" readonly="true" id="titletext"/>
       <grid>
         <columns>
           <column/>
--- a/toolkit/components/console/content/console.xul
+++ b/toolkit/components/console/content/console.xul
@@ -107,17 +107,17 @@
       <toolbarbutton type="radio" group="mode" id="Console:modeMessages"
                    label="&messages.label;" accesskey="&messages.accesskey;"
                    oncommand="changeMode('Messages');"/>
       <toolbarseparator/>
       <toolbarbutton id="Console:clear" oncommand="clearConsole();"
                      label="&clear.label;" accesskey="&clear.accesskey;"/>
     </toolbar>
   
-    <toolbar class="chromeclass-toolbar" id="ToolbarEval" align="center">
+    <toolbar class="chromeclass-toolbar" id="ToolbarEval" align="center" nowindowdrag="true">
       <label value="&codeEval.label;" accesskey="&codeEval.accesskey;" control="TextboxEval"/>
       <textbox id="TextboxEval" class="toolbar" value="" onkeypress="onEvalKeyPress(event)" flex="1"/>
       <toolbarbutton id="ButtonEval" label="&evaluate.label;"
                      accesskey="&evaluate.accesskey;" oncommand="evaluateTypein()"/>
     </toolbar>
   </toolbox>
   
   <vbox id="ConsoleBox" class="console-box" flex="1" context="ConsoleContext" persist="sortOrder"/>
--- a/toolkit/content/Makefile.in
+++ b/toolkit/content/Makefile.in
@@ -73,12 +73,16 @@ SOURCE_REPO := $(shell cd $(topsrcdir) &
 ifeq (http,$(patsubst http%,http,$(SOURCE_REPO)))
 DEFINES += -DSOURCE_REPO="$(SOURCE_REPO)"
 endif
 
 ifdef ENABLE_TESTS
 DIRS += tests
 endif
 
-EXTRA_JS_MODULES = debug.js
+EXTRA_JS_MODULES = \
+  debug.js \
+  WindowDraggingUtils.jsm \
+  $(NULL)
+
 EXTRA_PP_JS_MODULES = debug.js
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/content/WindowDraggingUtils.jsm
@@ -0,0 +1,87 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org Code.
+ *
+ * The Initial Developer of the Original Code is
+ * Markus Stange <mstange@themasta.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+let EXPORTED_SYMBOLS = [ "WindowDraggingElement" ];
+
+function WindowDraggingElement(elem, window) {
+  this._elem = elem;
+  this._window = window;
+  this._elem.addEventListener("mousedown", this, false);
+}
+
+WindowDraggingElement.prototype = {
+  mouseDownCheck: function(e) { return true; },
+  dragTags: ["box", "hbox", "vbox", "spacer", "label", "statusbarpanel",
+             "toolbaritem", "toolbarseparator", "toolbarspring", "toolbarspacer",
+             "radiogroup"],
+  handleEvent: function(aEvent) {
+    switch (aEvent.type) {
+      case "mousedown":
+        if (aEvent.button != 0 || !this.mouseDownCheck.call(this._elem, aEvent))
+          return;
+
+        let target = aEvent.target, parent = aEvent.target;
+        while (parent != this._elem) {
+          let mousethrough = parent.getAttribute("mousethrough");
+          if (mousethrough == "always")
+            target = parent.parentNode;
+          else if (mousethrough == "never")
+            break;
+          parent = parent.parentNode;
+        }
+        while (target != this._elem) {
+          if (this.dragTags.indexOf(target.localName) == -1)
+            return;
+          target = target.parentNode;
+        }
+        this._deltaX = aEvent.screenX - this._window.screenX;
+        this._deltaY = aEvent.screenY - this._window.screenY;
+        this._draggingWindow = true;
+        this._window.addEventListener("mousemove", this, false);
+        this._window.addEventListener("mouseup", this, false);
+        break;
+      case "mousemove":
+        if (this._draggingWindow)
+          this._window.moveTo(aEvent.screenX - this._deltaX, aEvent.screenY - this._deltaY);
+        break;
+      case "mouseup":
+        this._draggingWindow = false;
+        this._window.removeEventListener("mousemove", this, false);
+        this._window.removeEventListener("mouseup", this, false);
+        break;
+    }
+  }
+}
--- a/toolkit/content/customizeToolbar.js
+++ b/toolkit/content/customizeToolbar.js
@@ -51,27 +51,29 @@ function onLoad()
   InitWithToolbox(window.arguments[0]);
   repositionDialog();
 }
 
 function InitWithToolbox(aToolbox)
 {
   gToolbox = aToolbox;
   gToolboxDocument = gToolbox.ownerDocument;
-  
+  gToolbox.customizing = true;
+
   gToolbox.addEventListener("draggesture", onToolbarDragGesture, false);
   gToolbox.addEventListener("dragover", onToolbarDragOver, false);
   gToolbox.addEventListener("dragexit", onToolbarDragExit, false);
   gToolbox.addEventListener("dragdrop", onToolbarDragDrop, false);
 
   initDialog();
 }
 
 function finishToolbarCustomization()
 {
+  gToolbox.customizing = false;
   removeToolboxListeners();
   unwrapToolbarItems();
   persistCurrentSets();
   
   notifyParentComplete();
 }
 
 function initDialog()
--- a/toolkit/content/widgets/general.xml
+++ b/toolkit/content/widgets/general.xml
@@ -280,16 +280,26 @@
           <![CDATA[
             return Components.interfaces.nsIAccessibleProvider.XULStatusBar;
           ]]>
         </getter>
       </property>
     </implementation>
   </binding>
   
+  <binding id="statusbar-drag"
+           extends="chrome://global/content/bindings/general.xml#statusbar">
+    <implementation>
+      <constructor>
+        Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm");
+        new WindowDraggingElement(this, window);
+      </constructor>
+    </implementation>
+  </binding>
+
   <binding id="statusbarpanel-iconic" display="xul:button"
            extends="chrome://global/content/bindings/general.xml#statusbarpanel">
     <content>
       <xul:image class="statusbarpanel-icon" xbl:inherits="src,src=image"/>
     </content>
   </binding>
 
   <binding id="statusbarpanel-iconic-text" display="xul:button"
@@ -370,9 +380,18 @@
           <![CDATA[
             return Components.interfaces.nsIAccessibleProvider.XULDropmarker;
           ]]>
         </getter>
       </property>
     </implementation>
   </binding>
 
+  <binding id="windowdragbox">
+    <implementation>
+      <constructor>
+        Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm");
+        new WindowDraggingElement(this, window);
+      </constructor>
+    </implementation>
+  </binding>
+
 </bindings>
--- a/toolkit/content/widgets/preferences.xml
+++ b/toolkit/content/widgets/preferences.xml
@@ -553,18 +553,20 @@
              closebuttonlabel="&preferencesCloseButton.label;"
              closebuttonaccesskey="&preferencesCloseButton.accesskey;"
              role="dialog"
 #ifdef XP_WIN
              title="&preferencesDefaultTitleWin.title;">
 #else
              title="&preferencesDefaultTitleMac.title;">
 #endif
-      <xul:radiogroup anonid="selector" orient="horizontal" class="paneSelector chromeclass-toolbar"
-                      role="listbox"/> <!-- Expose to accessibility APIs as a listbox -->
+      <xul:windowdragbox orient="vertical">
+        <xul:radiogroup anonid="selector" orient="horizontal" class="paneSelector chromeclass-toolbar"
+                        role="listbox"/> <!-- Expose to accessibility APIs as a listbox -->
+      </xul:windowdragbox>
       <xul:hbox flex="1" class="paneDeckContainer">
         <xul:deck anonid="paneDeck" flex="1">
           <children includes="prefpane"/>
         </xul:deck>
       </xul:hbox>
       <xul:hbox anonid="dlg-buttons" class="prefWindow-dlgbuttons"
 #ifdef XP_UNIX
                 >
--- a/toolkit/content/widgets/toolbar.xml
+++ b/toolkit/content/widgets/toolbar.xml
@@ -20,16 +20,21 @@
       <field name="toolbarset">
         null
       </field>
       
       <field name="customToolbarCount">
         0
       </field>
       
+      <!-- Set by customizeToolbar.js -->
+      <field name="customizing">
+        false
+      </field>
+
       <constructor>
         <![CDATA[
           // Look to see if there is a toolbarset.
           this.toolbarset = this.firstChild;
           while (this.toolbarset && this.toolbarset.localName != "toolbarset")
             this.toolbarset = toolbarset.nextSibling;
           
           if (this.toolbarset) {
@@ -264,16 +269,29 @@
                
             return newItem;
           ]]>
         </body>
       </method>      
     </implementation>
   </binding>
 
+  <binding id="toolbar-drag"
+           extends="chrome://global/content/bindings/toolbar.xml#toolbar">
+    <implementation>
+      <constructor>
+        Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm");
+        let draggableThis = new WindowDraggingElement(this, window);
+        draggableThis.mouseDownCheck = function(e) {
+          return !this.parentNode.customizing; // Don't move while customizing.
+        }
+      </constructor>
+    </implementation>
+  </binding>
+
   <binding id="menubar" extends="chrome://global/content/bindings/toolbar.xml#toolbar-base" display="xul:menubar">
     <implementation implements="nsIAccessibleProvider">
       <property name="accessibleType" readonly="true">
         <getter>
           <![CDATA[
             return Components.interfaces.nsIAccessibleProvider.XULMenubar;
           ]]>
         </getter>
--- a/toolkit/mozapps/downloads/content/downloads.xul
+++ b/toolkit/mozapps/downloads/content/downloads.xul
@@ -178,19 +178,19 @@
 
   <richlistbox id="downloadView" seltype="multiple" flex="1"
                context="downloadContextMenu"
                ondblclick="onDownloadDblClick(event);"
                ondragover="nsDragAndDrop.dragOver(event, gDownloadDNDObserver);"
                ondragdrop="nsDragAndDrop.drop(event, gDownloadDNDObserver);">
   </richlistbox>
 
-  <hbox id="search" align="center">
+  <windowdragbox id="search" align="center">
     <button id="clearListButton" command="cmd_clearList"
             label="&cmd.clearList.label;"
             accesskey="&cmd.clearList.accesskey;"
             tooltiptext="&cmd.clearList.tooltip;"/>
     <spacer flex="1"/>
     <textbox type="search" id="searchbox"
              oncommand="buildDownloadList();" emptytext="&searchBox.label;"/>
-  </hbox>
+  </windowdragbox>
 
 </window>
--- a/toolkit/mozapps/extensions/content/extensions.xul
+++ b/toolkit/mozapps/extensions/content/extensions.xul
@@ -154,37 +154,39 @@
     <menuitem id="menuitem_learnMore" command="cmd_homepage"
               label="&searchResultHomepage.value;"/>
     <menuitem id="menuitem_installSearchResult" command="cmd_installSearchResult"
               label="&cmd.installSearchResult.label;"/>
   </vbox>
 
   <popup id="addonContextMenu" onpopupshowing="return buildContextMenu(event);"/>
 
-  <stack id="topStackBar">
-    <radiogroup id="viewGroup" xhtml:role="listbox" persist="last-selected"
-                class="viewSelector chromeclass-toolbar" orient="horizontal">
-      <radio id="search-view" label="&search.label;" oncommand="showView('search');" persist="last-selected"/>
-      <radio id="extensions-view" label="&extensions.label;" oncommand="showView('extensions');" persist="last-selected"/>
-      <radio id="themes-view" label="&themes.label;" oncommand="showView('themes');" persist="last-selected"/>
-      <radio id="locales-view" label="&locales.label;" oncommand="showView('locales');" persist="last-selected"/>
-      <radio id="plugins-view" label="&plugins.label;" oncommand="showView('plugins');" persist="last-selected"/>
-      <radio id="updates-view" label="&update.label;" oncommand="showView('updates');"/>
-      <radio id="installs-view" label="&install.label;" oncommand="showView('installs');" hidden="true"/>
-    </radiogroup>
-    <vbox id="progressBox" hidden="true" class="viewSelector" flex="1">
-      <spacer flex="1"/>
-      <hbox>
-        <image class="addonThrobber"/>
-        <label id="progressStatus" value="&progressStatus.label;"/>
-      </hbox>
-      <progressmeter id="addonsProgress" class="extension-item-progress" flex="1"/>
-      <spacer flex="1"/>
-    </vbox>
-  </stack>
+  <windowdragbox orient="vertical">
+    <stack id="topStackBar">
+      <radiogroup id="viewGroup" xhtml:role="listbox" persist="last-selected"
+                  class="viewSelector chromeclass-toolbar" orient="horizontal">
+        <radio id="search-view" label="&search.label;" oncommand="showView('search');" persist="last-selected"/>
+        <radio id="extensions-view" label="&extensions.label;" oncommand="showView('extensions');" persist="last-selected"/>
+        <radio id="themes-view" label="&themes.label;" oncommand="showView('themes');" persist="last-selected"/>
+        <radio id="locales-view" label="&locales.label;" oncommand="showView('locales');" persist="last-selected"/>
+        <radio id="plugins-view" label="&plugins.label;" oncommand="showView('plugins');" persist="last-selected"/>
+        <radio id="updates-view" label="&update.label;" oncommand="showView('updates');"/>
+        <radio id="installs-view" label="&install.label;" oncommand="showView('installs');" hidden="true"/>
+      </radiogroup>
+      <vbox id="progressBox" hidden="true" class="viewSelector" flex="1">
+        <spacer flex="1"/>
+        <hbox>
+          <image class="addonThrobber"/>
+          <label id="progressStatus" value="&progressStatus.label;"/>
+        </hbox>
+        <progressmeter id="addonsProgress" class="extension-item-progress" flex="1"/>
+        <spacer flex="1"/>
+      </vbox>
+    </stack>
+  </windowdragbox>
   <notificationbox id="addonsMsg" flex="1">
     <vbox id="extensionsBox" flex="1">
       <hbox id="searchPanel" align="center">
         <textbox id="searchfield" emptytext="&searchAddons.label;"
                  type="search" searchbutton="true"
                  oncommand="retrieveRepositoryAddons(this.value);"/>
         <spacer flex="1"/>
         <label id="browseAddons" class="text-link" value="&browseAddons.label;"
--- a/toolkit/themes/pinstripe/global/global.css
+++ b/toolkit/themes/pinstripe/global/global.css
@@ -55,16 +55,30 @@ menulist > menupopup,
 .menulist-compact {
   -moz-binding: url("chrome://global/content/bindings/menulist.xml#menulist-compact");
 }
 
 progressmeter[mode="undetermined"] {
   -moz-binding: url("chrome://global/content/bindings/progressmeter.xml#progressmeter-periodic-redraw");
 }
 
+/* ::::: draggable elements ::::: */
+
+toolbar:not([nowindowdrag="true"]) {
+  -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbar-drag");
+}
+
+statusbar:not([nowindowdrag="true"]) {
+  -moz-binding: url("chrome://global/content/bindings/general.xml#statusbar-drag");
+}
+
+windowdragbox {
+  -moz-binding: url("chrome://global/content/bindings/general.xml#windowdragbox");
+}
+
 /* ::::: root elements ::::: */
 
 window,
 page,
 dialog,
 wizard,
 prefwindow { 
   -moz-appearance: dialog;