/** * @license * Visual Blocks Editor * * Copyright 2011 Google Inc. * https://developers.google.com/blockly/ * * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @fileoverview Functionality for the right-click context menus. * @author fraser@google.com (Neil Fraser) */ 'use strict'; goog.provide('Blockly.ContextMenu'); goog.require('goog.dom'); goog.require('goog.events'); goog.require('goog.style'); goog.require('goog.ui.Menu'); goog.require('goog.ui.MenuItem'); /** * Which block is the context menu attached to? * @type {Blockly.Block} */ Blockly.ContextMenu.currentBlock = null; /** * Construct the menu based on the list of options and show the menu. * @param {!Event} e Mouse event. * @param {!Array.} options Array of menu options. * @param {boolean} rtl True if RTL, false if LTR. */ Blockly.ContextMenu.show = function(e, options, rtl) { Blockly.WidgetDiv.show(Blockly.ContextMenu, rtl, null); if (!options.length) { Blockly.ContextMenu.hide(); return; } /* Here's what one option object looks like: {text: 'Make It So', enabled: true, callback: Blockly.MakeItSo} */ var menu = new goog.ui.Menu(); menu.setRightToLeft(rtl); for (var i = 0, option; option = options[i]; i++) { var menuItem = new goog.ui.MenuItem(option.text); menuItem.setRightToLeft(rtl); menu.addChild(menuItem, true); menuItem.setEnabled(option.enabled); if (option.enabled) { goog.events.listen(menuItem, goog.ui.Component.EventType.ACTION, option.callback); menuItem.handleContextMenu = function(e) { // Right-clicking on menu option should count as a click. goog.events.dispatchEvent(this, goog.ui.Component.EventType.ACTION); }; } } goog.events.listen(menu, goog.ui.Component.EventType.ACTION, Blockly.ContextMenu.hide); // Record windowSize and scrollOffset before adding menu. var windowSize = goog.dom.getViewportSize(); var scrollOffset = goog.style.getViewportPageOffset(document); var div = Blockly.WidgetDiv.DIV; menu.render(div); var menuDom = menu.getElement(); Blockly.addClass_(menuDom, 'blocklyContextMenu'); // Prevent system context menu when right-clicking a Blockly context menu. Blockly.bindEventWithChecks_(menuDom, 'contextmenu', null, Blockly.noEvent); // Record menuSize after adding menu. var menuSize = goog.style.getSize(menuDom); // Position the menu. var x = e.clientX + scrollOffset.x; var y = e.clientY + scrollOffset.y; // Flip menu vertically if off the bottom. if (e.clientY + menuSize.height >= windowSize.height) { y -= menuSize.height; } // Flip menu horizontally if off the edge. if (rtl) { if (menuSize.width >= e.clientX) { x += menuSize.width; } } else { if (e.clientX + menuSize.width >= windowSize.width) { x -= menuSize.width; } } Blockly.WidgetDiv.position(x, y, windowSize, scrollOffset, rtl); menu.setAllowAutoFocus(true); // 1ms delay is required for focusing on context menus because some other // mouse event is still waiting in the queue and clears focus. setTimeout(function() {menuDom.focus();}, 1); Blockly.ContextMenu.currentBlock = null; // May be set by Blockly.Block. }; /** * Hide the context menu. */ Blockly.ContextMenu.hide = function() { Blockly.WidgetDiv.hideIfOwner(Blockly.ContextMenu); Blockly.ContextMenu.currentBlock = null; }; /** * Create a callback function that creates and configures a block, * then places the new block next to the original. * @param {!Blockly.Block} block Original block. * @param {!Element} xml XML representation of new block. * @return {!Function} Function that creates a block. */ Blockly.ContextMenu.callbackFactory = function(block, xml) { return function() { Blockly.Events.disable(); try { var newBlock = Blockly.Xml.domToBlock(xml, block.workspace); // Move the new block next to the old block. var xy = block.getRelativeToSurfaceXY(); if (block.RTL) { xy.x -= Blockly.SNAP_RADIUS; } else { xy.x += Blockly.SNAP_RADIUS; } xy.y += Blockly.SNAP_RADIUS * 2; newBlock.moveBy(xy.x, xy.y); } finally { Blockly.Events.enable(); } if (Blockly.Events.isEnabled() && !newBlock.isShadow()) { Blockly.Events.fire(new Blockly.Events.Create(newBlock)); } newBlock.select(); }; };