scratch-blocks/core/contextmenu.js

154 lines
4.9 KiB
JavaScript
Raw Normal View History

/**
* @license
* Visual Blocks Editor
*
* Copyright 2011 Google Inc.
2014-10-07 13:09:55 -07:00
* 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');
2014-09-08 14:26:52 -07:00
goog.require('goog.dom');
goog.require('goog.events');
2014-09-08 14:26:52 -07:00
goog.require('goog.style');
goog.require('goog.ui.Menu');
goog.require('goog.ui.MenuItem');
/**
2014-09-08 14:26:52 -07:00
* Which block is the context menu attached to?
* @type {Blockly.Block}
*/
2014-09-08 14:26:52 -07:00
Blockly.ContextMenu.currentBlock = null;
/**
* Construct the menu based on the list of options and show the menu.
2014-09-08 14:26:52 -07:00
* @param {!Event} e Mouse event.
* @param {!Array.<!Object>} options Array of menu options.
2015-04-28 13:51:25 -07:00
* @param {boolean} rtl True if RTL, false if LTR.
*/
2015-04-28 13:51:25 -07:00
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}
*/
2014-09-08 14:26:52 -07:00
var menu = new goog.ui.Menu();
2015-04-28 13:51:25 -07:00
menu.setRightToLeft(rtl);
2016-05-04 15:05:45 -07:00
for (var i = 0, option; option = options[i]; i++) {
2014-09-08 14:26:52 -07:00
var menuItem = new goog.ui.MenuItem(option.text);
2015-04-28 13:51:25 -07:00
menuItem.setRightToLeft(rtl);
2014-09-08 14:26:52 -07:00
menu.addChild(menuItem, true);
menuItem.setEnabled(option.enabled);
if (option.enabled) {
2014-09-08 14:26:52 -07:00
goog.events.listen(menuItem, goog.ui.Component.EventType.ACTION,
2016-01-08 13:03:22 -08:00
option.callback);
2016-11-02 13:56:27 -07:00
menuItem.handleContextMenu = function(/* e */) {
// Right-clicking on menu option should count as a click.
goog.events.dispatchEvent(this, goog.ui.Component.EventType.ACTION);
};
}
}
2014-09-08 14:26:52 -07:00
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.utils.addClass(menuDom, 'blocklyContextMenu');
// Prevent system context menu when right-clicking a Blockly context menu.
Blockly.bindEventWithChecks_(menuDom, 'contextmenu', null,
Blockly.utils.noEvent);
2014-09-08 14:26:52 -07:00
// Record menuSize after adding menu.
var menuSize = goog.style.getSize(menuDom);
2014-09-08 14:26:52 -07:00
// 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;
}
2014-09-08 14:26:52 -07:00
// Flip menu horizontally if off the edge.
2015-04-28 13:51:25 -07:00
if (rtl) {
2014-09-08 14:26:52 -07:00
if (menuSize.width >= e.clientX) {
x += menuSize.width;
}
} else {
2014-09-08 14:26:52 -07:00
if (e.clientX + menuSize.width >= windowSize.width) {
x -= menuSize.width;
}
}
2015-04-28 13:51:25 -07:00
Blockly.WidgetDiv.position(x, y, windowSize, scrollOffset, rtl);
2014-09-08 14:26:52 -07:00
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() {
2014-09-08 14:26:52 -07:00
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();
};
};