menu.xml 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. <?xml version="1.0"?>
  2. <!-- This Source Code Form is subject to the terms of the Mozilla Public
  3. License, v. 2.0. If a copy of the MPL was not distributed with this
  4. file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
  5. <bindings id="placesMenuBindings"
  6. xmlns="http://www.mozilla.org/xbl"
  7. xmlns:xbl="http://www.mozilla.org/xbl"
  8. xmlns:html="http://www.w3.org/1999/xhtml"
  9. xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  10. <binding id="places-popup-base"
  11. extends="chrome://global/content/bindings/popup.xml#popup">
  12. <content>
  13. <xul:hbox flex="1">
  14. <xul:vbox class="menupopup-drop-indicator-bar" hidden="true">
  15. <xul:image class="menupopup-drop-indicator" mousethrough="always"/>
  16. </xul:vbox>
  17. <xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
  18. smoothscroll="false">
  19. <children/>
  20. </xul:arrowscrollbox>
  21. </xul:hbox>
  22. </content>
  23. <implementation>
  24. <field name="_indicatorBar">
  25. document.getAnonymousElementByAttribute(this, "class",
  26. "menupopup-drop-indicator-bar");
  27. </field>
  28. <field name="_scrollBox">
  29. document.getAnonymousElementByAttribute(this, "class",
  30. "popup-internal-box");
  31. </field>
  32. <!-- This is the view that manage the popup -->
  33. <field name="_rootView">PlacesUIUtils.getViewForNode(this);</field>
  34. <!-- Check if we should hide the drop indicator for the target -->
  35. <method name="_hideDropIndicator">
  36. <parameter name="aEvent"/>
  37. <body><![CDATA[
  38. let target = aEvent.target;
  39. // Don't draw the drop indicator outside of markers.
  40. // The markers are hidden, since otherwise sometimes popups acquire
  41. // scrollboxes on OS X, so we can't use them directly.
  42. let firstChildTop = this._startMarker.nextSibling.boxObject.y;
  43. let lastChildBottom = this._endMarker.previousSibling.boxObject.y +
  44. this._endMarker.previousSibling.boxObject.height;
  45. let betweenMarkers = target.boxObject.y >= firstChildTop ||
  46. target.boxObject.y <= lastChildBottom;
  47. // Hide the dropmarker if current node is not a Places node.
  48. return !(target && target._placesNode && betweenMarkers);
  49. ]]></body>
  50. </method>
  51. <!-- This function returns information about where to drop when
  52. dragging over this popup insertion point -->
  53. <method name="_getDropPoint">
  54. <parameter name="aEvent"/>
  55. <body><![CDATA[
  56. // Can't drop if the menu isn't a folder
  57. let resultNode = this._placesNode;
  58. if (!PlacesUtils.nodeIsFolder(resultNode) ||
  59. PlacesControllerDragHelper.disallowInsertion(resultNode)) {
  60. return null;
  61. }
  62. var dropPoint = { ip: null, folderElt: null };
  63. // The element we are dragging over
  64. let elt = aEvent.target;
  65. if (elt.localName == "menupopup")
  66. elt = elt.parentNode;
  67. // Calculate positions taking care of arrowscrollbox
  68. let eventY = aEvent.layerY;
  69. let scrollbox = this._scrollBox;
  70. let scrollboxOffset = scrollbox.scrollBoxObject.y -
  71. (scrollbox.boxObject.y - this.boxObject.y);
  72. let eltY = elt.boxObject.y - scrollboxOffset;
  73. let eltHeight = elt.boxObject.height;
  74. if (!elt._placesNode) {
  75. // If we are dragging over a non places node drop at the end.
  76. dropPoint.ip = new InsertionPoint(
  77. PlacesUtils.getConcreteItemId(resultNode),
  78. -1,
  79. Ci.nsITreeView.DROP_ON);
  80. // We can set folderElt if we are dropping over a static menu that
  81. // has an internal placespopup.
  82. let isMenu = elt.localName == "menu" ||
  83. (elt.localName == "toolbarbutton" &&
  84. elt.getAttribute("type") == "menu");
  85. if (isMenu && elt.lastChild &&
  86. elt.lastChild.hasAttribute("placespopup"))
  87. dropPoint.folderElt = elt;
  88. return dropPoint;
  89. }
  90. if ((PlacesUtils.nodeIsFolder(elt._placesNode) &&
  91. !PlacesUIUtils.isContentsReadOnly(elt._placesNode)) ||
  92. PlacesUtils.nodeIsTagQuery(elt._placesNode)) {
  93. // This is a folder or a tag container.
  94. if (eventY - eltY < eltHeight * 0.20) {
  95. // If mouse is in the top part of the element, drop above folder.
  96. dropPoint.ip = new InsertionPoint(
  97. PlacesUtils.getConcreteItemId(resultNode),
  98. -1,
  99. Ci.nsITreeView.DROP_BEFORE,
  100. PlacesUtils.nodeIsTagQuery(elt._placesNode),
  101. elt._placesNode.itemId);
  102. return dropPoint;
  103. }
  104. else if (eventY - eltY < eltHeight * 0.80) {
  105. // If mouse is in the middle of the element, drop inside folder.
  106. dropPoint.ip = new InsertionPoint(
  107. PlacesUtils.getConcreteItemId(elt._placesNode),
  108. -1,
  109. Ci.nsITreeView.DROP_ON,
  110. PlacesUtils.nodeIsTagQuery(elt._placesNode));
  111. dropPoint.folderElt = elt;
  112. return dropPoint;
  113. }
  114. }
  115. else if (eventY - eltY <= eltHeight / 2) {
  116. // This is a non-folder node or a readonly folder.
  117. // If the mouse is above the middle, drop above this item.
  118. dropPoint.ip = new InsertionPoint(
  119. PlacesUtils.getConcreteItemId(resultNode),
  120. -1,
  121. Ci.nsITreeView.DROP_BEFORE,
  122. PlacesUtils.nodeIsTagQuery(elt._placesNode),
  123. elt._placesNode.itemId);
  124. return dropPoint;
  125. }
  126. // Drop below the item.
  127. dropPoint.ip = new InsertionPoint(
  128. PlacesUtils.getConcreteItemId(resultNode),
  129. -1,
  130. Ci.nsITreeView.DROP_AFTER,
  131. PlacesUtils.nodeIsTagQuery(elt._placesNode),
  132. elt._placesNode.itemId);
  133. return dropPoint;
  134. ]]></body>
  135. </method>
  136. <!-- Sub-menus should be opened when the mouse drags over them, and closed
  137. when the mouse drags off. The overFolder object manages opening and
  138. closing of folders when the mouse hovers. -->
  139. <field name="_overFolder"><![CDATA[({
  140. _self: this,
  141. _folder: {elt: null,
  142. openTimer: null,
  143. hoverTime: 350,
  144. closeTimer: null},
  145. _closeMenuTimer: null,
  146. get elt() {
  147. return this._folder.elt;
  148. },
  149. set elt(val) {
  150. return this._folder.elt = val;
  151. },
  152. get openTimer() {
  153. return this._folder.openTimer;
  154. },
  155. set openTimer(val) {
  156. return this._folder.openTimer = val;
  157. },
  158. get hoverTime() {
  159. return this._folder.hoverTime;
  160. },
  161. set hoverTime(val) {
  162. return this._folder.hoverTime = val;
  163. },
  164. get closeTimer() {
  165. return this._folder.closeTimer;
  166. },
  167. set closeTimer(val) {
  168. return this._folder.closeTimer = val;
  169. },
  170. get closeMenuTimer() {
  171. return this._closeMenuTimer;
  172. },
  173. set closeMenuTimer(val) {
  174. return this._closeMenuTimer = val;
  175. },
  176. setTimer: function OF__setTimer(aTime) {
  177. var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  178. timer.initWithCallback(this, aTime, timer.TYPE_ONE_SHOT);
  179. return timer;
  180. },
  181. notify: function OF__notify(aTimer) {
  182. // Function to process all timer notifications.
  183. if (aTimer == this._folder.openTimer) {
  184. // Timer to open a submenu that's being dragged over.
  185. this._folder.elt.lastChild.setAttribute("autoopened", "true");
  186. this._folder.elt.lastChild.showPopup(this._folder.elt);
  187. this._folder.openTimer = null;
  188. }
  189. else if (aTimer == this._folder.closeTimer) {
  190. // Timer to close a submenu that's been dragged off of.
  191. // Only close the submenu if the mouse isn't being dragged over any
  192. // of its child menus.
  193. var draggingOverChild = PlacesControllerDragHelper
  194. .draggingOverChildNode(this._folder.elt);
  195. if (draggingOverChild)
  196. this._folder.elt = null;
  197. this.clear();
  198. // Close any parent folders which aren't being dragged over.
  199. // (This is necessary because of the above code that keeps a folder
  200. // open while its children are being dragged over.)
  201. if (!draggingOverChild)
  202. this.closeParentMenus();
  203. }
  204. else if (aTimer == this.closeMenuTimer) {
  205. // Timer to close this menu after the drag exit.
  206. var popup = this._self;
  207. // if we are no more dragging we can leave the menu open to allow
  208. // for better D&D bookmark organization
  209. if (PlacesControllerDragHelper.getSession() &&
  210. !PlacesControllerDragHelper.draggingOverChildNode(popup.parentNode)) {
  211. popup.hidePopup();
  212. // Close any parent menus that aren't being dragged over;
  213. // otherwise they'll stay open because they couldn't close
  214. // while this menu was being dragged over.
  215. this.closeParentMenus();
  216. }
  217. this._closeMenuTimer = null;
  218. }
  219. },
  220. // Helper function to close all parent menus of this menu,
  221. // as long as none of the parent's children are currently being
  222. // dragged over.
  223. closeParentMenus: function OF__closeParentMenus() {
  224. var popup = this._self;
  225. var parent = popup.parentNode;
  226. while (parent) {
  227. if (parent.localName == "menupopup" && parent._placesNode) {
  228. if (PlacesControllerDragHelper.draggingOverChildNode(parent.parentNode))
  229. break;
  230. parent.hidePopup();
  231. }
  232. parent = parent.parentNode;
  233. }
  234. },
  235. // The mouse is no longer dragging over the stored menubutton.
  236. // Close the menubutton, clear out drag styles, and clear all
  237. // timers for opening/closing it.
  238. clear: function OF__clear() {
  239. if (this._folder.elt && this._folder.elt.lastChild) {
  240. if (!this._folder.elt.lastChild.hasAttribute("dragover"))
  241. this._folder.elt.lastChild.hidePopup();
  242. // remove menuactive style
  243. this._folder.elt.removeAttribute("_moz-menuactive");
  244. this._folder.elt = null;
  245. }
  246. if (this._folder.openTimer) {
  247. this._folder.openTimer.cancel();
  248. this._folder.openTimer = null;
  249. }
  250. if (this._folder.closeTimer) {
  251. this._folder.closeTimer.cancel();
  252. this._folder.closeTimer = null;
  253. }
  254. }
  255. })]]></field>
  256. <method name="_cleanupDragDetails">
  257. <body><![CDATA[
  258. // Called on dragend and drop.
  259. PlacesControllerDragHelper.currentDropTarget = null;
  260. this._rootView._draggedElt = null;
  261. this.removeAttribute("dragover");
  262. this.removeAttribute("dragstart");
  263. this._indicatorBar.hidden = true;
  264. ]]></body>
  265. </method>
  266. </implementation>
  267. <handlers>
  268. <handler event="DOMMenuItemActive"><![CDATA[
  269. let elt = event.target;
  270. if (elt.parentNode != this)
  271. return;
  272. if (window.XULBrowserWindow) {
  273. let elt = event.target;
  274. let placesNode = elt._placesNode;
  275. var linkURI;
  276. if (placesNode && PlacesUtils.nodeIsURI(placesNode))
  277. linkURI = placesNode.uri;
  278. else if (elt.hasAttribute("targetURI"))
  279. linkURI = elt.getAttribute("targetURI");
  280. if (linkURI)
  281. window.XULBrowserWindow.setOverLink(linkURI, null);
  282. }
  283. ]]></handler>
  284. <handler event="DOMMenuItemInactive"><![CDATA[
  285. let elt = event.target;
  286. if (elt.parentNode != this)
  287. return;
  288. if (window.XULBrowserWindow)
  289. window.XULBrowserWindow.setOverLink("", null);
  290. ]]></handler>
  291. <handler event="dragstart"><![CDATA[
  292. if (!event.target._placesNode)
  293. return;
  294. let draggedElt = event.target._placesNode;
  295. // Force a copy action if parent node is a query or we are dragging a
  296. // not-removable node.
  297. if (!PlacesControllerDragHelper.canMoveNode(draggedElt))
  298. event.dataTransfer.effectAllowed = "copyLink";
  299. // Activate the view and cache the dragged element.
  300. this._rootView._draggedElt = draggedElt;
  301. this._rootView.controller.setDataTransfer(event);
  302. this.setAttribute("dragstart", "true");
  303. event.stopPropagation();
  304. ]]></handler>
  305. <handler event="drop"><![CDATA[
  306. PlacesControllerDragHelper.currentDropTarget = event.target;
  307. let dropPoint = this._getDropPoint(event);
  308. if (dropPoint && dropPoint.ip) {
  309. PlacesControllerDragHelper.onDrop(dropPoint.ip, event.dataTransfer);
  310. event.preventDefault();
  311. }
  312. this._cleanupDragDetails();
  313. event.stopPropagation();
  314. ]]></handler>
  315. <handler event="dragover"><![CDATA[
  316. PlacesControllerDragHelper.currentDropTarget = event.target;
  317. let dt = event.dataTransfer;
  318. let dropPoint = this._getDropPoint(event);
  319. if (!dropPoint || !dropPoint.ip ||
  320. !PlacesControllerDragHelper.canDrop(dropPoint.ip, dt)) {
  321. this._indicatorBar.hidden = true;
  322. event.stopPropagation();
  323. return;
  324. }
  325. // Mark this popup as being dragged over.
  326. this.setAttribute("dragover", "true");
  327. if (dropPoint.folderElt) {
  328. // We are dragging over a folder.
  329. // _overFolder should take the care of opening it on a timer.
  330. if (this._overFolder.elt &&
  331. this._overFolder.elt != dropPoint.folderElt) {
  332. // We are dragging over a new folder, let's clear old values
  333. this._overFolder.clear();
  334. }
  335. if (!this._overFolder.elt) {
  336. this._overFolder.elt = dropPoint.folderElt;
  337. // Create the timer to open this folder.
  338. this._overFolder.openTimer = this._overFolder
  339. .setTimer(this._overFolder.hoverTime);
  340. }
  341. // Since we are dropping into a folder set the corresponding style.
  342. dropPoint.folderElt.setAttribute("_moz-menuactive", true);
  343. }
  344. else {
  345. // We are not dragging over a folder.
  346. // Clear out old _overFolder information.
  347. this._overFolder.clear();
  348. }
  349. // Autoscroll the popup strip if we drag over the scroll buttons.
  350. let anonid = event.originalTarget.getAttribute('anonid');
  351. let scrollDir = anonid == "scrollbutton-up" ? -1 :
  352. anonid == "scrollbutton-down" ? 1 : 0;
  353. if (scrollDir != 0) {
  354. this._scrollBox.scrollByIndex(scrollDir, false);
  355. }
  356. // Check if we should hide the drop indicator for this target.
  357. if (dropPoint.folderElt || this._hideDropIndicator(event)) {
  358. this._indicatorBar.hidden = true;
  359. event.preventDefault();
  360. event.stopPropagation();
  361. return;
  362. }
  363. // We should display the drop indicator relative to the arrowscrollbox.
  364. let sbo = this._scrollBox.scrollBoxObject;
  365. let newMarginTop = 0;
  366. if (scrollDir == 0) {
  367. let elt = this.firstChild;
  368. while (elt && event.screenY > elt.boxObject.screenY +
  369. elt.boxObject.height / 2)
  370. elt = elt.nextSibling;
  371. newMarginTop = elt ? elt.boxObject.screenY - sbo.screenY :
  372. sbo.height;
  373. }
  374. else if (scrollDir == 1)
  375. newMarginTop = sbo.height;
  376. // Set the new marginTop based on arrowscrollbox.
  377. newMarginTop += sbo.y - this._scrollBox.boxObject.y;
  378. this._indicatorBar.firstChild.style.marginTop = newMarginTop + "px";
  379. this._indicatorBar.hidden = false;
  380. event.preventDefault();
  381. event.stopPropagation();
  382. ]]></handler>
  383. <handler event="dragexit"><![CDATA[
  384. PlacesControllerDragHelper.currentDropTarget = null;
  385. this.removeAttribute("dragover");
  386. // If we have not moved to a valid new target clear the drop indicator
  387. // this happens when moving out of the popup.
  388. let target = event.relatedTarget;
  389. if (!target)
  390. this._indicatorBar.hidden = true;
  391. // Close any folder being hovered over
  392. if (this._overFolder.elt) {
  393. this._overFolder.closeTimer = this._overFolder
  394. .setTimer(this._overFolder.hoverTime);
  395. }
  396. // The autoopened attribute is set when this folder was automatically
  397. // opened after the user dragged over it. If this attribute is set,
  398. // auto-close the folder on drag exit.
  399. // We should also try to close this popup if the drag has started
  400. // from here, the timer will check if we are dragging over a child.
  401. if (this.hasAttribute("autoopened") ||
  402. this.hasAttribute("dragstart")) {
  403. this._overFolder.closeMenuTimer = this._overFolder
  404. .setTimer(this._overFolder.hoverTime);
  405. }
  406. event.stopPropagation();
  407. ]]></handler>
  408. <handler event="dragend"><![CDATA[
  409. this._cleanupDragDetails();
  410. ]]></handler>
  411. </handlers>
  412. </binding>
  413. </bindings>