contextmenu.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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. }
  64. }
  65. goog.events.listen(menu, goog.ui.Component.EventType.ACTION,
  66. Blockly.ContextMenu.hide);
  67. // Record windowSize and scrollOffset before adding menu.
  68. var windowSize = goog.dom.getViewportSize();
  69. var scrollOffset = goog.style.getViewportPageOffset(document);
  70. var div = Blockly.WidgetDiv.DIV;
  71. menu.render(div);
  72. var menuDom = menu.getElement();
  73. Blockly.addClass_(menuDom, 'blocklyContextMenu');
  74. // Prevent system context menu when right-clicking a Blockly context menu.
  75. Blockly.bindEvent_(menuDom, 'contextmenu', null, Blockly.noEvent);
  76. // Record menuSize after adding menu.
  77. var menuSize = goog.style.getSize(menuDom);
  78. // Position the menu.
  79. var x = e.clientX + scrollOffset.x;
  80. var y = e.clientY + scrollOffset.y;
  81. // Flip menu vertically if off the bottom.
  82. if (e.clientY + menuSize.height >= windowSize.height) {
  83. y -= menuSize.height;
  84. }
  85. // Flip menu horizontally if off the edge.
  86. if (rtl) {
  87. if (menuSize.width >= e.clientX) {
  88. x += menuSize.width;
  89. }
  90. } else {
  91. if (e.clientX + menuSize.width >= windowSize.width) {
  92. x -= menuSize.width;
  93. }
  94. }
  95. Blockly.WidgetDiv.position(x, y, windowSize, scrollOffset, rtl);
  96. menu.setAllowAutoFocus(true);
  97. // 1ms delay is required for focusing on context menus because some other
  98. // mouse event is still waiting in the queue and clears focus.
  99. setTimeout(function() {menuDom.focus();}, 1);
  100. Blockly.ContextMenu.currentBlock = null; // May be set by Blockly.Block.
  101. };
  102. /**
  103. * Hide the context menu.
  104. */
  105. Blockly.ContextMenu.hide = function() {
  106. Blockly.WidgetDiv.hideIfOwner(Blockly.ContextMenu);
  107. Blockly.ContextMenu.currentBlock = null;
  108. };
  109. /**
  110. * Create a callback function that creates and configures a block,
  111. * then places the new block next to the original.
  112. * @param {!Blockly.Block} block Original block.
  113. * @param {!Element} xml XML representation of new block.
  114. * @return {!Function} Function that creates a block.
  115. */
  116. Blockly.ContextMenu.callbackFactory = function(block, xml) {
  117. return function() {
  118. Blockly.Events.disable();
  119. try {
  120. var newBlock = Blockly.Xml.domToBlock(xml, block.workspace);
  121. // Move the new block next to the old block.
  122. var xy = block.getRelativeToSurfaceXY();
  123. if (block.RTL) {
  124. xy.x -= Blockly.SNAP_RADIUS;
  125. } else {
  126. xy.x += Blockly.SNAP_RADIUS;
  127. }
  128. xy.y += Blockly.SNAP_RADIUS * 2;
  129. newBlock.moveBy(xy.x, xy.y);
  130. } finally {
  131. Blockly.Events.enable();
  132. }
  133. if (Blockly.Events.isEnabled() && !newBlock.isShadow()) {
  134. Blockly.Events.fire(new Blockly.Events.Create(newBlock));
  135. }
  136. newBlock.select();
  137. };
  138. };