contextmenu.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. /**
  2. * @license
  3. * Visual Blocks Editor
  4. *
  5. * Copyright 2011 Google Inc.
  6. * https://developers.google.com/blockly/
  7. *
  8. * Licensed under the Apache License, Version 2.0 (the "License");
  9. * you may not use this file except in compliance with the License.
  10. * You may obtain a copy of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS,
  16. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. * See the License for the specific language governing permissions and
  18. * limitations under the License.
  19. */
  20. /**
  21. * @fileoverview Functionality for the right-click context menus.
  22. * @author fraser@google.com (Neil Fraser)
  23. */
  24. 'use strict';
  25. goog.provide('Blockly.ContextMenu');
  26. goog.require('goog.dom');
  27. goog.require('goog.events');
  28. goog.require('goog.style');
  29. goog.require('goog.ui.Menu');
  30. goog.require('goog.ui.MenuItem');
  31. /**
  32. * Which block is the context menu attached to?
  33. * @type {Blockly.Block}
  34. */
  35. Blockly.ContextMenu.currentBlock = null;
  36. /**
  37. * Construct the menu based on the list of options and show the menu.
  38. * @param {!Event} e Mouse event.
  39. * @param {!Array.<!Object>} options Array of menu options.
  40. * @param {boolean} rtl True if RTL, false if LTR.
  41. */
  42. Blockly.ContextMenu.show = function(e, options, rtl) {
  43. Blockly.WidgetDiv.show(Blockly.ContextMenu, rtl, null);
  44. if (!options.length) {
  45. Blockly.ContextMenu.hide();
  46. return;
  47. }
  48. /* Here's what one option object looks like:
  49. {text: 'Make It So',
  50. enabled: true,
  51. callback: Blockly.MakeItSo}
  52. */
  53. var menu = new goog.ui.Menu();
  54. menu.setRightToLeft(rtl);
  55. for (var i = 0, option; option = options[i]; i++) {
  56. var menuItem = new goog.ui.MenuItem(option.text);
  57. menuItem.setRightToLeft(rtl);
  58. menu.addChild(menuItem, true);
  59. menuItem.setEnabled(option.enabled);
  60. if (option.enabled) {
  61. goog.events.listen(menuItem, goog.ui.Component.EventType.ACTION,
  62. option.callback);
  63. menuItem.handleContextMenu = function(e) {
  64. // Right-clicking on menu option should count as a click.
  65. goog.events.dispatchEvent(this, goog.ui.Component.EventType.ACTION);
  66. };
  67. }
  68. }
  69. goog.events.listen(menu, goog.ui.Component.EventType.ACTION,
  70. Blockly.ContextMenu.hide);
  71. // Record windowSize and scrollOffset before adding menu.
  72. var windowSize = goog.dom.getViewportSize();
  73. var scrollOffset = goog.style.getViewportPageOffset(document);
  74. var div = Blockly.WidgetDiv.DIV;
  75. menu.render(div);
  76. var menuDom = menu.getElement();
  77. Blockly.addClass_(menuDom, 'blocklyContextMenu');
  78. // Prevent system context menu when right-clicking a Blockly context menu.
  79. Blockly.bindEventWithChecks_(menuDom, 'contextmenu', null, Blockly.noEvent);
  80. // Record menuSize after adding menu.
  81. var menuSize = goog.style.getSize(menuDom);
  82. // Position the menu.
  83. var x = e.clientX + scrollOffset.x;
  84. var y = e.clientY + scrollOffset.y;
  85. // Flip menu vertically if off the bottom.
  86. if (e.clientY + menuSize.height >= windowSize.height) {
  87. y -= menuSize.height;
  88. }
  89. // Flip menu horizontally if off the edge.
  90. if (rtl) {
  91. if (menuSize.width >= e.clientX) {
  92. x += menuSize.width;
  93. }
  94. } else {
  95. if (e.clientX + menuSize.width >= windowSize.width) {
  96. x -= menuSize.width;
  97. }
  98. }
  99. Blockly.WidgetDiv.position(x, y, windowSize, scrollOffset, rtl);
  100. menu.setAllowAutoFocus(true);
  101. // 1ms delay is required for focusing on context menus because some other
  102. // mouse event is still waiting in the queue and clears focus.
  103. setTimeout(function() {menuDom.focus();}, 1);
  104. Blockly.ContextMenu.currentBlock = null; // May be set by Blockly.Block.
  105. };
  106. /**
  107. * Hide the context menu.
  108. */
  109. Blockly.ContextMenu.hide = function() {
  110. Blockly.WidgetDiv.hideIfOwner(Blockly.ContextMenu);
  111. Blockly.ContextMenu.currentBlock = null;
  112. };
  113. /**
  114. * Create a callback function that creates and configures a block,
  115. * then places the new block next to the original.
  116. * @param {!Blockly.Block} block Original block.
  117. * @param {!Element} xml XML representation of new block.
  118. * @return {!Function} Function that creates a block.
  119. */
  120. Blockly.ContextMenu.callbackFactory = function(block, xml) {
  121. return function() {
  122. Blockly.Events.disable();
  123. try {
  124. var newBlock = Blockly.Xml.domToBlock(xml, block.workspace);
  125. // Move the new block next to the old block.
  126. var xy = block.getRelativeToSurfaceXY();
  127. if (block.RTL) {
  128. xy.x -= Blockly.SNAP_RADIUS;
  129. } else {
  130. xy.x += Blockly.SNAP_RADIUS;
  131. }
  132. xy.y += Blockly.SNAP_RADIUS * 2;
  133. newBlock.moveBy(xy.x, xy.y);
  134. } finally {
  135. Blockly.Events.enable();
  136. }
  137. if (Blockly.Events.isEnabled() && !newBlock.isShadow()) {
  138. Blockly.Events.fire(new Blockly.Events.Create(newBlock));
  139. }
  140. newBlock.select();
  141. };
  142. };