diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d48fe19a99..745a25ee65 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2918,10 +2918,12 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; case Qt::Key_P: { - bool isFirstPersonChecked = Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson); - Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, !isFirstPersonChecked); - Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, isFirstPersonChecked); - cameraMenuChanged(); + if (!(isShifted || isMeta || isOption)) { + bool isFirstPersonChecked = Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson); + Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, !isFirstPersonChecked); + Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, isFirstPersonChecked); + cameraMenuChanged(); + } break; } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index acf97ad5f7..c131367aee 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -577,7 +577,7 @@ Menu::Menu() { nodeList.data(), SLOT(toggleSendNewerDSConnectVersion(bool))); #endif - + // Developer >> Tests >>> MenuWrapper* testMenu = developerMenu->addMenu("Tests"); addActionToQMenuAndActionHash(testMenu, MenuOption::RunClientScriptTests, 0, dialogsManager.data(), SLOT(showTestingResults())); @@ -628,9 +628,9 @@ Menu::Menu() { auto scope = DependencyManager::get(); MenuWrapper* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope"); - addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_P, false, + addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_F2, false, scope.data(), SLOT(toggle())); - addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopePause, Qt::CTRL | Qt::SHIFT | Qt::Key_P, false, + addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopePause, Qt::CTRL | Qt::SHIFT | Qt::Key_F2, false, scope.data(), SLOT(togglePause())); addDisabledActionAndSeparator(audioScopeMenu, "Display Frames"); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index ad3af3a659..a440fec1ac 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -56,6 +56,7 @@ selectionManager.addEventListener(function () { lightOverlayManager.updatePositions(); }); +const KEY_P = 80; //Key code for letter p used for Parenting hotkey. var DEGREES_TO_RADIANS = Math.PI / 180.0; var RADIANS_TO_DEGREES = 180.0 / Math.PI; var epsilon = 0.001; @@ -843,7 +844,6 @@ function setupModelMenus() { }); modelMenuAddedDelete = true; } - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Entity List...", @@ -851,11 +851,25 @@ function setupModelMenus() { afterItem: "Entities", grouping: "Advanced" }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Parent Entity to Last", + afterItem: "Entity List...", + grouping: "Advanced" + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Unparent Entity", + afterItem: "Parent Entity to Last", + grouping: "Advanced" + }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", shortcutKey: "CTRL+META+L", - afterItem: "Entity List...", + afterItem: "Unparent Entity", isCheckable: true, isChecked: true, grouping: "Advanced" @@ -958,6 +972,8 @@ function cleanupModelMenus() { Menu.removeMenuItem("Edit", "Delete"); } + Menu.removeMenuItem("Edit", "Parent Entity to Last"); + Menu.removeMenuItem("Edit", "Unparent Entity"); Menu.removeMenuItem("Edit", "Entity List..."); Menu.removeMenuItem("Edit", "Allow Selecting of Large Models"); Menu.removeMenuItem("Edit", "Allow Selecting of Small Models"); @@ -990,6 +1006,9 @@ Script.scriptEnding.connect(function () { Overlays.deleteOverlay(importingSVOImageOverlay); Overlays.deleteOverlay(importingSVOTextOverlay); + + Controller.keyReleaseEvent.disconnect(keyReleaseEvent); + Controller.keyPressEvent.disconnect(keyPressEvent); }); var lastOrientation = null; @@ -1101,7 +1120,68 @@ function recursiveDelete(entities, childrenList) { Entities.deleteEntity(entityID); } } +function unparentSelectedEntities() { + if (SelectionManager.hasSelection()) { + var selectedEntities = selectionManager.selections; + var parentCheck = false; + if (selectedEntities.length < 1) { + Window.notifyEditError("You must have an entity selected inorder to unparent it."); + return; + } + selectedEntities.forEach(function (id, index) { + var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; + if (parentId !== null && parentId.length > 0 && parentId !== "{00000000-0000-0000-0000-000000000000}") { + parentCheck = true; + } + Entities.editEntity(id, {parentID: null}) + return true; + }); + if (parentCheck) { + if (selectedEntities.length > 1) { + Window.notify("Entities unparented"); + } else { + Window.notify("Entity unparented"); + } + } else { + if (selectedEntities.length > 1) { + Window.notify("Selected Entities have no parents"); + } else { + Window.notify("Selected Entity does not have a parent"); + } + } + } else { + Window.notifyEditError("You have nothing selected to unparent"); + } +} +function parentSelectedEntities() { + if (SelectionManager.hasSelection()) { + var selectedEntities = selectionManager.selections; + if (selectedEntities.length <= 1) { + Window.notifyEditError("You must have multiple entities selected in order to parent them"); + return; + } + var parentCheck = false; + var lastEntityId = selectedEntities[selectedEntities.length-1]; + selectedEntities.forEach(function (id, index) { + if (lastEntityId !== id) { + var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; + if (parentId !== lastEntityId) { + parentCheck = true; + } + Entities.editEntity(id, {parentID: lastEntityId}) + } + }); + + if(parentCheck) { + Window.notify("Entities parented"); + }else { + Window.notify("Entities are already parented to last"); + } + } else { + Window.notifyEditError("You have nothing selected to parent"); + } +} function deleteSelectedEntities() { if (SelectionManager.hasSelection()) { selectedParticleEntity = 0; @@ -1164,6 +1244,10 @@ function handeMenuEvent(menuItem) { Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights")); } else if (menuItem === "Delete") { deleteSelectedEntities(); + } else if (menuItem === "Parent Entity to Last") { + parentSelectedEntities(); + } else if (menuItem === "Unparent Entity") { + unparentSelectedEntities(); } else if (menuItem === "Export Entities") { if (!selectionManager.hasSelection()) { Window.notifyEditError("No entities have been selected."); @@ -1289,13 +1373,12 @@ Window.svoImportRequested.connect(importSVO); Menu.menuItemEvent.connect(handeMenuEvent); -Controller.keyPressEvent.connect(function (event) { +var keyPressEvent = function (event) { if (isActive) { cameraManager.keyPressEvent(event); } -}); - -Controller.keyReleaseEvent.connect(function (event) { +}; +var keyReleaseEvent = function (event) { if (isActive) { cameraManager.keyReleaseEvent(event); } @@ -1329,8 +1412,16 @@ Controller.keyReleaseEvent.connect(function (event) { }); grid.setPosition(newPosition); } + } else if (event.key === KEY_P && event.isControl && !event.isAutoRepeat ) { + if (event.isShifted) { + unparentSelectedEntities(); + } else { + parentSelectedEntities(); + } } -}); +}; +Controller.keyReleaseEvent.connect(keyReleaseEvent); +Controller.keyPressEvent.connect(keyPressEvent); function recursiveAdd(newParentID, parentData) { var children = parentData.children; @@ -1580,6 +1671,10 @@ var PropertiesTool = function (opts) { } pushCommandForSelections(); selectionManager._update(); + } else if(data.type === 'parent') { + parentSelectedEntities(); + } else if(data.type === 'unparent') { + unparentSelectedEntities(); } else if(data.type === 'saveUserData'){ //the event bridge and json parsing handle our avatar id string differently. var actualID = data.id.split('"')[1]; @@ -1837,6 +1932,9 @@ var PopupMenu = function () { for (var i = 0; i < overlays.length; i++) { Overlays.deleteOverlay(overlays[i]); } + Controller.mousePressEvent.disconnect(self.mousePressEvent); + Controller.mouseMoveEvent.disconnect(self.mouseMoveEvent); + Controller.mouseReleaseEvent.disconnect(self.mouseReleaseEvent); } Controller.mousePressEvent.connect(self.mousePressEvent); @@ -1864,7 +1962,11 @@ var particleExplorerTool = new ParticleExplorerTool(); var selectedParticleEntity = 0; entityListTool.webView.webEventReceived.connect(function (data) { data = JSON.parse(data); - if (data.type === "selectionUpdate") { + if(data.type === 'parent') { + parentSelectedEntities(); + } else if(data.type === 'unparent') { + unparentSelectedEntities(); + } else if (data.type === "selectionUpdate") { var ids = data.entityIds; if (ids.length === 1) { if (Entities.getEntityProperties(ids[0], "type").type === "ParticleEffect") { diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 197d8f550a..3cb79353f9 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -89,6 +89,7 @@ +
No entities found in view within a 100 meter radius. Try moving to a different location and refreshing.
diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index b11127b26c..cc73d71517 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -61,7 +61,7 @@ - +

diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 1af9c1e1d6..914fc6525e 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -19,6 +19,7 @@ const VISIBLE_GLYPH = ""; const TRANSPARENCY_GLYPH = ""; const SCRIPT_GLYPH = "k"; const DELETE = 46; // Key code for the delete key. +const KEY_P = 80; // Key code for letter p used for Parenting hotkey. const MAX_ITEMS = Number.MAX_VALUE; // Used to set the max length of the list of discovered entities. debugPrint = function (message) { @@ -26,7 +27,7 @@ debugPrint = function (message) { }; function loaded() { - openEventBridge(function() { + openEventBridge(function() { entityList = new List('entity-list', { valueNames: ['name', 'type', 'url', 'locked', 'visible'], page: MAX_ITEMS}); entityList.clear(); elEntityTable = document.getElementById("entity-table"); @@ -48,7 +49,7 @@ function loaded() { elNoEntitiesInView = document.getElementById("no-entities-in-view"); elNoEntitiesRadius = document.getElementById("no-entities-radius"); elEntityTableScroll = document.getElementById("entity-table-scroll"); - + document.getElementById("entity-name").onclick = function() { setSortColumn('name'); }; @@ -90,7 +91,7 @@ function loaded() { selection = selection.concat(selectedEntities); } else if (clickEvent.shiftKey && selectedEntities.length > 0) { var previousItemFound = -1; - var clickedItemFound = -1; + var clickedItemFound = -1; for (var entity in entityList.visibleItems) { if (clickedItemFound === -1 && this.dataset.entityId == entityList.visibleItems[entity].values().id) { clickedItemFound = entity; @@ -113,11 +114,11 @@ function loaded() { selection = selection.concat(betweenItems, selectedEntities); } } - + selectedEntities = selection; - + this.className = 'selected'; - + EventBridge.emitWebEvent(JSON.stringify({ type: "selectionUpdate", focus: false, @@ -126,7 +127,7 @@ function loaded() { refreshFooter(); } - + function onRowDoubleClicked() { EventBridge.emitWebEvent(JSON.stringify({ type: "selectionUpdate", @@ -134,7 +135,7 @@ function loaded() { entityIds: [this.dataset.entityId], })); } - + const BYTES_PER_MEGABYTE = 1024 * 1024; function decimalMegabytes(number) { @@ -173,7 +174,7 @@ function loaded() { currentElement.onclick = onRowClicked; currentElement.ondblclick = onRowDoubleClicked; }); - + if (refreshEntityListTimer) { clearTimeout(refreshEntityListTimer); } @@ -183,13 +184,13 @@ function loaded() { item.values({ name: name, url: filename, locked: locked, visible: visible }); } } - + function clearEntities() { entities = {}; entityList.clear(); refreshFooter(); } - + var elSortOrder = { name: document.querySelector('#entity-name .sort-order'), type: document.querySelector('#entity-type .sort-order'), @@ -215,12 +216,12 @@ function loaded() { entityList.sort(currentSortColumn, { order: currentSortOrder }); } setSortColumn('type'); - + function refreshEntities() { clearEntities(); EventBridge.emitWebEvent(JSON.stringify({ type: 'refresh' })); } - + function refreshFooter() { if (selectedEntities.length > 1) { elFooter.firstChild.nodeValue = selectedEntities.length + " entities selected"; @@ -239,7 +240,7 @@ function loaded() { entityList.search(elFilter.value); refreshFooter(); } - + function updateSelectedEntities(selectedIDs) { var notFound = false; for (var id in entities) { @@ -262,7 +263,7 @@ function loaded() { return notFound; } - + elRefresh.onclick = function() { refreshEntities(); } @@ -282,7 +283,7 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); refreshEntities(); } - + document.addEventListener("keydown", function (keyDownEvent) { if (keyDownEvent.target.nodeName === "INPUT") { return; @@ -292,8 +293,15 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); refreshEntities(); } + if (keyDownEvent.keyCode === KEY_P && keyDownEvent.ctrlKey) { + if (keyDownEvent.shiftKey) { + EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); + } else { + EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' })); + } + } }, false); - + var isFilterInView = false; var FILTER_IN_VIEW_ATTRIBUTE = "pressed"; elNoEntitiesInView.style.display = "none"; @@ -320,7 +328,7 @@ function loaded() { if (window.EventBridge !== undefined) { EventBridge.scriptEventReceived.connect(function(data) { data = JSON.parse(data); - + if (data.type === "clearEntityList") { clearEntities(); } else if (data.type == "selectionUpdate") { @@ -426,4 +434,3 @@ function loaded() { event.preventDefault(); }, false); } - diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 8879c0f34e..c51293f4bc 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -24,7 +24,7 @@ var ICON_FOR_TYPE = { } var EDITOR_TIMEOUT_DURATION = 1500; - +const KEY_P = 80; //Key code for letter p used for Parenting hotkey. var colorPickers = []; var lastEntityID = null; debugPrint = function(message) { @@ -273,7 +273,7 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen propertyValue += subPropertyString + ','; } } else { - // We've unchecked, so remove + // We've unchecked, so remove propertyValue = propertyValue.replace(subPropertyString + ",", ""); } @@ -780,7 +780,7 @@ function loaded() { if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null && editor !== null) { saveJSONUserData(true); } - //the event bridge and json parsing handle our avatar id string differently. + //the event bridge and json parsing handle our avatar id string differently. lastEntityID = '"' + properties.id + '"'; elID.innerHTML = properties.id; @@ -1390,7 +1390,7 @@ function loaded() { elZoneFlyingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('flyingAllowed')); elZoneGhostingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('ghostingAllowed')); elZoneFilterURL.addEventListener('change', createEmitTextPropertyUpdateFunction('filterURL')); - + var voxelVolumeSizeChangeFunction = createEmitVec3PropertyUpdateFunction( 'voxelVolumeSize', elVoxelVolumeSizeX, elVoxelVolumeSizeY, elVoxelVolumeSizeZ); elVoxelVolumeSizeX.addEventListener('change', voxelVolumeSizeChangeFunction); @@ -1441,7 +1441,15 @@ function loaded() { })); }); - + document.addEventListener("keydown", function (keyDown) { + if (keyDown.keyCode === KEY_P && keyDown.ctrlKey) { + if (keyDown.shiftKey) { + EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); + } else { + EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' })); + } + } + }); window.onblur = function() { // Fake a change event var ev = document.createEvent("HTMLEvents"); diff --git a/scripts/system/html/js/gridControls.js b/scripts/system/html/js/gridControls.js index a245ed4cda..be4271788e 100644 --- a/scripts/system/html/js/gridControls.js +++ b/scripts/system/html/js/gridControls.js @@ -6,6 +6,8 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +const KEY_P = 80; //Key code for letter p used for Parenting hotkey. + function loaded() { openEventBridge(function() { elPosY = document.getElementById("horiz-y"); @@ -131,10 +133,17 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: 'init' })); }); - + document.addEventListener("keydown", function (keyDown) { + if (keyDown.keyCode === KEY_P && keyDown.ctrlKey) { + if (keyDown.shiftKey) { + EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); + } else { + EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' })); + } + } + }) // Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked document.addEventListener("contextmenu", function (event) { event.preventDefault(); }, false); } - diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 9c1626caf4..d68a525458 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1170,14 +1170,14 @@ SelectionDisplay = (function() { // determine which bottom corner we are closest to /*------------------------------ example: - + BRF +--------+ BLF | | | | BRN +--------+ BLN - + * - + ------------------------------*/ var cameraPosition = Camera.getPosition(); @@ -2189,8 +2189,12 @@ SelectionDisplay = (function() { offset = Vec3.multiplyQbyV(properties.rotation, offset); var boxPosition = Vec3.sum(properties.position, offset); + var color = {red: 255, green: 128, blue: 0}; + if (i >= selectionManager.selections.length - 1) color = {red: 255, green: 255, blue: 64}; + Overlays.editOverlay(selectionBoxes[i], { position: boxPosition, + color: color, rotation: properties.rotation, dimensions: properties.dimensions, visible: true, @@ -2395,7 +2399,7 @@ SelectionDisplay = (function() { if (wantDebug) { print("Start Elevation: " + translateXZTool.startingElevation + ", elevation: " + elevation); } - if ((translateXZTool.startingElevation > 0.0 && elevation < MIN_ELEVATION) || + if ((translateXZTool.startingElevation > 0.0 && elevation < MIN_ELEVATION) || (translateXZTool.startingElevation < 0.0 && elevation > -MIN_ELEVATION)) { if (wantDebug) { print("too close to horizon!"); @@ -3857,7 +3861,7 @@ SelectionDisplay = (function() { }; that.mousePressEvent = function(event) { - var wantDebug = false; + var wantDebug = false; if (!event.isLeftButton && !that.triggered) { // if another mouse button than left is pressed ignore it return false; @@ -3889,7 +3893,7 @@ SelectionDisplay = (function() { if (result.intersects) { - + if (wantDebug) { print("something intersects... "); print(" result.overlayID:" + result.overlayID + "[" + overlayNames[result.overlayID] + "]"); @@ -3989,7 +3993,7 @@ SelectionDisplay = (function() { if (wantDebug) { print("rotate handle case..."); } - + // After testing our stretch handles, then check out rotate handles Overlays.editOverlay(yawHandle, { @@ -4211,7 +4215,7 @@ SelectionDisplay = (function() { case selectionBox: activeTool = translateXZTool; translateXZTool.pickPlanePosition = result.intersection; - translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), + translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), SelectionManager.worldDimensions.z); if (wantDebug) { print("longest dimension: " + translateXZTool.greatestDimension); @@ -4220,7 +4224,7 @@ SelectionDisplay = (function() { translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition); print(" starting elevation: " + translateXZTool.startingElevation); } - + mode = translateXZTool.mode; activeTool.onBegin(event); somethingClicked = 'selectionBox'; diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 3ae071c7e3..b2ebb1fd46 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -521,6 +521,9 @@ function onEditError(msg) { createNotification(wordWrap(msg), NotificationType.EDIT_ERROR); } +function onNotify(msg) { + createNotification(wordWrap(msg), NotificationType.UNKNOWN); // Needs a generic notification system for user feedback, thus using this +} function onSnapshotTaken(pathStillSnapshot, pathAnimatedSnapshot, notify) { if (notify) { @@ -637,6 +640,7 @@ Window.domainConnectionRefused.connect(onDomainConnectionRefused); Window.snapshotTaken.connect(onSnapshotTaken); Window.processingGif.connect(processingGif); Window.notifyEditError = onEditError; +Window.notify = onNotify; setup();