Merge branch 'master' of github.com:highfidelity/hifi

This commit is contained in:
Babiuch, Ryan Nicholas 2016-02-05 12:49:38 -06:00
commit aab98db812
26 changed files with 351 additions and 1117 deletions

View file

@ -23,7 +23,6 @@ Script.include([
"libraries/ToolTip.js",
"libraries/entityPropertyDialogBox.js",
"libraries/entityCameraTool.js",
"libraries/gridTool.js",
"libraries/entityList.js",
@ -32,7 +31,6 @@ Script.include([
var selectionDisplay = SelectionDisplay;
var selectionManager = SelectionManager;
var entityPropertyDialogBox = EntityPropertyDialogBox;
var lightOverlayManager = new LightOverlayManager();

View file

@ -1,457 +0,0 @@
//
// entityPropertyDialogBox.js
// examples
//
// Created by Brad hefta-Gaub on 10/1/14.
// Copyright 2014 High Fidelity, Inc.
//
// This script implements a class useful for building tools for editing entities.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var DEGREES_TO_RADIANS = Math.PI / 180.0;
var RADIANS_TO_DEGREES = 180.0 / Math.PI;
EntityPropertyDialogBox = (function () {
var that = {};
var propertiesForEditedEntity;
var editEntityFormArray;
var decimals = 3;
var dimensionX;
var dimensionY;
var dimensionZ;
var rescalePercentage;
var editModelID = -1;
var previousAnimationIsPlaying;
var previousAnimationCurrentFrame;
that.cleanup = function () {
};
that.openDialog = function (entityID) {
print(" Edit Properties.... about to edit properties...");
editModelID = entityID;
propertiesForEditedEntity = Entities.getEntityProperties(editModelID);
var properties = propertiesForEditedEntity;
var array = new Array();
var index = 0;
array.push({ label: "Entity Type:" + properties.type, type: "header" });
index++;
array.push({ label: "ID:", value: properties.id });
index++;
array.push({ label: "Locked:", type: "checkbox", value: properties.locked });
index++;
if (properties.type == "Model") {
array.push({ label: "Model URL:", value: properties.modelURL });
index++;
array.push({ label: "Shape Type:", value: properties.shapeType });
index++;
array.push({ label: "Compound Shape URL:", value: properties.compoundShapeURL });
index++;
array.push({ label: "Animation URL:", value: properties.animation.url });
index++;
array.push({ label: "Animation is playing:", type: "checkbox", value: properties.animation.running });
previousAnimationIsPlaying = properties.animation.running;
index++;
array.push({ label: "Animation FPS:", value: properties.animation.fps });
index++;
array.push({ label: "Animation Frame:", value: properties.animation.currentFrame });
previousAnimationCurrentFrame = properties.animation.currentFrame;
index++;
array.push({ label: "Textures:", value: properties.textures });
index++;
array.push({ label: "Original Textures:\n" + properties.originalTextures, type: "header" });
index++;
}
if (properties.type == "Text") {
array.push({ label: "Text:", value: properties.text });
index++;
array.push({ label: "Line Height:", value: properties.lineHeight });
index++;
array.push({ label: "Text Color:", type: "header" });
index++;
array.push({ label: "Red:", value: properties.textColor.red });
index++;
array.push({ label: "Green:", value: properties.textColor.green });
index++;
array.push({ label: "Blue:", value: properties.textColor.blue });
index++;
array.push({ label: "Background Color:", type: "header" });
index++;
array.push({ label: "Red:", value: properties.backgroundColor.red });
index++;
array.push({ label: "Green:", value: properties.backgroundColor.green });
index++;
array.push({ label: "Blue:", value: properties.backgroundColor.blue });
index++;
}
if (properties.type == "PolyVox") {
array.push({ label: "Voxel Space Size:", type: "header" });
index++;
array.push({ label: "X:", value: properties.voxelVolumeSize.x.toFixed(decimals) });
index++;
array.push({ label: "Y:", value: properties.voxelVolumeSize.y.toFixed(decimals) });
index++;
array.push({ label: "Z:", value: properties.voxelVolumeSize.z.toFixed(decimals) });
index++;
array.push({ label: "Surface Extractor", value: properties.voxelSurfaceStyle });
index++;
array.push({ label: "X-axis Texture URL:", value: properties.xTextureURL });
index++;
array.push({ label: "Y-axis Texture URL:", value: properties.yTextureURL });
index++;
array.push({ label: "Z-axis Texture URL:", value: properties.zTextureURL });
index++;
}
array.push({ label: "Position:", type: "header" });
index++;
array.push({ label: "X:", value: properties.position.x.toFixed(decimals) });
index++;
array.push({ label: "Y:", value: properties.position.y.toFixed(decimals) });
index++;
array.push({ label: "Z:", value: properties.position.z.toFixed(decimals) });
index++;
array.push({ label: "Registration X:", value: properties.registrationPoint.x.toFixed(decimals) });
index++;
array.push({ label: "Registration Y:", value: properties.registrationPoint.y.toFixed(decimals) });
index++;
array.push({ label: "Registration Z:", value: properties.registrationPoint.z.toFixed(decimals) });
index++;
array.push({ label: "Rotation:", type: "header" });
index++;
var angles = Quat.safeEulerAngles(properties.rotation);
array.push({ label: "Pitch:", value: angles.x.toFixed(decimals) });
index++;
array.push({ label: "Yaw:", value: angles.y.toFixed(decimals) });
index++;
array.push({ label: "Roll:", value: angles.z.toFixed(decimals) });
index++;
array.push({ label: "Dimensions:", type: "header" });
index++;
array.push({ label: "Width:", value: properties.dimensions.x.toFixed(decimals) });
dimensionX = index;
index++;
array.push({ label: "Height:", value: properties.dimensions.y.toFixed(decimals) });
dimensionY = index;
index++;
array.push({ label: "Depth:", value: properties.dimensions.z.toFixed(decimals) });
dimensionZ = index;
index++;
array.push({ label: "", type: "inlineButton", buttonLabel: "Reset to Natural Dimensions", name: "resetDimensions" });
index++;
array.push({ label: "Rescale Percentage:", value: 100 });
rescalePercentage = index;
index++;
array.push({ label: "", type: "inlineButton", buttonLabel: "Rescale", name: "rescaleDimensions" });
index++;
array.push({ label: "Velocity:", type: "header" });
index++;
array.push({ label: "Linear X:", value: properties.velocity.x.toFixed(decimals) });
index++;
array.push({ label: "Linear Y:", value: properties.velocity.y.toFixed(decimals) });
index++;
array.push({ label: "Linear Z:", value: properties.velocity.z.toFixed(decimals) });
index++;
array.push({ label: "Linear Damping:", value: properties.damping.toFixed(decimals) });
index++;
// NOTE: angular velocity is in radians/sec but we display degrees/sec for users
array.push({ label: "Angular Pitch:", value: (properties.angularVelocity.x * RADIANS_TO_DEGREES).toFixed(decimals) });
index++;
array.push({ label: "Angular Yaw:", value: (properties.angularVelocity.y * RADIANS_TO_DEGREES).toFixed(decimals) });
index++;
array.push({ label: "Angular Roll:", value: (properties.angularVelocity.z * RADIANS_TO_DEGREES).toFixed(decimals) });
index++;
array.push({ label: "Angular Damping:", value: properties.angularDamping.toFixed(decimals) });
index++;
array.push({ label: "Gravity X:", value: properties.gravity.x.toFixed(decimals) });
index++;
array.push({ label: "Gravity Y:", value: properties.gravity.y.toFixed(decimals) });
index++;
array.push({ label: "Gravity Z:", value: properties.gravity.z.toFixed(decimals) });
index++;
array.push({ label: "Acceleration X:", value: properties.acceleration.x.toFixed(decimals) });
index++;
array.push({ label: "Acceleration Y:", value: properties.acceleration.y.toFixed(decimals) });
index++;
array.push({ label: "Acceleration Z:", value: properties.acceleration.z.toFixed(decimals) });
index++;
array.push({ label: "Collisions:", type: "header" });
index++;
array.push({ label: "Density:", value: properties.density.toFixed(decimals) });
index++;
array.push({ label: "Collisionless:", type: "checkbox", value: properties.collisionless });
index++;
array.push({ label: "Dynamic:", type: "checkbox", value: properties.dynamic });
index++;
array.push({ label: "Collision Sound URL:", value: properties.collisionSoundURL });
index++;
array.push({ label: "Lifetime:", value: properties.lifetime.toFixed(decimals) });
index++;
array.push({ label: "Visible:", type: "checkbox", value: properties.visible });
index++;
array.push({ label: "Script:", value: properties.script });
index++;
if (properties.type == "Box" || properties.type == "Sphere") {
array.push({ label: "Color:", type: "header" });
index++;
array.push({ label: "Red:", value: properties.color.red });
index++;
array.push({ label: "Green:", value: properties.color.green });
index++;
array.push({ label: "Blue:", value: properties.color.blue });
index++;
}
if (properties.type == "Light") {
array.push({ label: "Light Properties:", type: "header" });
index++;
array.push({ label: "Is Spot Light:", value: properties.isSpotlight });
index++;
array.push({ label: "Diffuse Red:", value: properties.diffuseColor.red });
index++;
array.push({ label: "Diffuse Green:", value: properties.diffuseColor.green });
index++;
array.push({ label: "Diffuse Blue:", value: properties.diffuseColor.blue });
index++;
array.push({ label: "Ambient Red:", value: properties.ambientColor.red });
index++;
array.push({ label: "Ambient Green:", value: properties.ambientColor.green });
index++;
array.push({ label: "Ambient Blue:", value: properties.ambientColor.blue });
index++;
array.push({ label: "Specular Red:", value: properties.specularColor.red });
index++;
array.push({ label: "Specular Green:", value: properties.specularColor.green });
index++;
array.push({ label: "Specular Blue:", value: properties.specularColor.blue });
index++;
array.push({ label: "Constant Attenuation:", value: properties.constantAttenuation });
index++;
array.push({ label: "Linear Attenuation:", value: properties.linearAttenuation });
index++;
array.push({ label: "Quadratic Attenuation:", value: properties.quadraticAttenuation });
index++;
array.push({ label: "Exponent:", value: properties.exponent });
index++;
array.push({ label: "Cutoff (in degrees):", value: properties.cutoff });
index++;
}
array.push({ label: "", type: "inlineButton", buttonLabel: "Preview Camera", name: "previewCamera" });
index++;
array.push({ button: "Cancel" });
index++;
editEntityFormArray = array;
Window.nonBlockingForm("Edit Properties", array);
};
Window.inlineButtonClicked.connect(function (name) {
if (name == "previewCamera") {
Camera.mode = "entity";
Camera.cameraEntity = propertiesForEditedEntity.id;
}
if (name == "resetDimensions") {
Window.reloadNonBlockingForm([
{ value: propertiesForEditedEntity.naturalDimensions.x.toFixed(decimals), oldIndex: dimensionX },
{ value: propertiesForEditedEntity.naturalDimensions.y.toFixed(decimals), oldIndex: dimensionY },
{ value: propertiesForEditedEntity.naturalDimensions.z.toFixed(decimals), oldIndex: dimensionZ }
]);
}
if (name == "rescaleDimensions") {
var peekValues = editEntityFormArray;
Window.peekNonBlockingFormResult(peekValues);
var peekX = peekValues[dimensionX].value;
var peekY = peekValues[dimensionY].value;
var peekZ = peekValues[dimensionZ].value;
var peekRescale = peekValues[rescalePercentage].value;
var rescaledX = peekX * peekRescale / 100.0;
var rescaledY = peekY * peekRescale / 100.0;
var rescaledZ = peekZ * peekRescale / 100.0;
Window.reloadNonBlockingForm([
{ value: rescaledX.toFixed(decimals), oldIndex: dimensionX },
{ value: rescaledY.toFixed(decimals), oldIndex: dimensionY },
{ value: rescaledZ.toFixed(decimals), oldIndex: dimensionZ },
{ value: 100, oldIndex: rescalePercentage }
]);
}
});
Window.nonBlockingFormClosed.connect(function() {
array = editEntityFormArray;
if (Window.getNonBlockingFormResult(array)) {
var properties = propertiesForEditedEntity;
var index = 0;
index++; // skip type header
index++; // skip id item
properties.locked = array[index++].value;
if (properties.type == "Model") {
properties.modelURL = array[index++].value;
properties.shapeType = array[index++].value;
properties.compoundShapeURL = array[index++].value;
properties.animation.url = array[index++].value;
var newAnimationIsPlaying = array[index++].value;
if (previousAnimationIsPlaying != newAnimationIsPlaying) {
properties.animation.running = newAnimationIsPlaying;
} else {
delete properties.animation.running;
}
properties.animation.fps = array[index++].value;
var newAnimationCurrentFrame = array[index++].value;
if (previousAnimationCurrentFrame != newAnimationCurrentFrame) {
properties.animation.currentFrame = newAnimationCurrentFrame;
} else {
delete properties.animation.currentFrame;
}
properties.textures = array[index++].value;
index++; // skip textureNames label
}
if (properties.type == "Text") {
properties.text = array[index++].value;
properties.lineHeight = array[index++].value;
index++; // skip header
properties.textColor.red = array[index++].value;
properties.textColor.green = array[index++].value;
properties.textColor.blue = array[index++].value;
index++; // skip header
properties.backgroundColor.red = array[index++].value;
properties.backgroundColor.green = array[index++].value;
properties.backgroundColor.blue = array[index++].value;
}
if (properties.type == "PolyVox") {
properties.shapeType = array[index++].value;
index++; // skip header
properties.voxelVolumeSize.x = array[index++].value;
properties.voxelVolumeSize.y = array[index++].value;
properties.voxelVolumeSize.z = array[index++].value;
properties.voxelSurfaceStyle = array[index++].value;
properties.xTextureURL = array[index++].value;
properties.yTextureURL = array[index++].value;
properties.zTextureURL = array[index++].value;
}
index++; // skip header
properties.position.x = array[index++].value;
properties.position.y = array[index++].value;
properties.position.z = array[index++].value;
properties.registrationPoint.x = array[index++].value;
properties.registrationPoint.y = array[index++].value;
properties.registrationPoint.z = array[index++].value;
index++; // skip header
var angles = Quat.safeEulerAngles(properties.rotation);
angles.x = array[index++].value;
angles.y = array[index++].value;
angles.z = array[index++].value;
properties.rotation = Quat.fromVec3Degrees(angles);
index++; // skip header
properties.dimensions.x = array[index++].value;
properties.dimensions.y = array[index++].value;
properties.dimensions.z = array[index++].value;
index++; // skip reset button
index++; // skip rescale percentage
index++; // skip rescale button
index++; // skip header
properties.velocity.x = array[index++].value;
properties.velocity.y = array[index++].value;
properties.velocity.z = array[index++].value;
properties.damping = array[index++].value;
// NOTE: angular velocity is in radians/sec but we display degrees/sec for users
properties.angularVelocity.x = array[index++].value * DEGREES_TO_RADIANS;
properties.angularVelocity.y = array[index++].value * DEGREES_TO_RADIANS;
properties.angularVelocity.z = array[index++].value * DEGREES_TO_RADIANS;
properties.angularDamping = array[index++].value;
properties.gravity.x = array[index++].value;
properties.gravity.y = array[index++].value;
properties.gravity.z = array[index++].value;
properties.acceleration.x = array[index++].value;
properties.acceleration.y = array[index++].value;
properties.acceleration.z = array[index++].value;
index++; // skip header
properties.density = array[index++].value;
properties.collisionless = array[index++].value;
properties.dynamic = array[index++].value;
properties.lifetime = array[index++].value;
properties.visible = array[index++].value;
properties.script = array[index++].value;
if (properties.type == "Box" || properties.type == "Sphere") {
index++; // skip header
properties.color.red = array[index++].value;
properties.color.green = array[index++].value;
properties.color.blue = array[index++].value;
}
if (properties.type == "Light") {
index++; // skip header
properties.isSpotlight = array[index++].value;
properties.diffuseColor.red = array[index++].value;
properties.diffuseColor.green = array[index++].value;
properties.diffuseColor.blue = array[index++].value;
properties.ambientColor.red = array[index++].value;
properties.ambientColor.green = array[index++].value;
properties.ambientColor.blue = array[index++].value;
properties.specularColor.red = array[index++].value;
properties.specularColor.green = array[index++].value;
properties.specularColor.blue = array[index++].value;
properties.constantAttenuation = array[index++].value;
properties.linearAttenuation = array[index++].value;
properties.quadraticAttenuation = array[index++].value;
properties.exponent = array[index++].value;
properties.cutoff = array[index++].value;
}
Entities.editEntity(editModelID, properties);
if (typeof(selectionDisplay) != "undefined") {
selectionDisplay.select(editModelID, false);
}
}
modelSelected = false;
});
return that;
}());

View file

@ -10,7 +10,8 @@ Original.CheckBox {
}
label: Text {
text: control.text
renderType: Text.QtRendering
}
}
}
}

View file

@ -0,0 +1,7 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
TextField {
style: TextFieldStyle { renderType: Text.QtRendering }
}

View file

@ -262,11 +262,10 @@ FocusScope {
}
Component { id: fileDialogBuilder; FileDialog { } }
function fileOpenDialog(properties) {
function fileDialog(properties) {
return fileDialogBuilder.createObject(desktop, properties);
}
MenuMouseHandler { id: menuPopperUpper }
function popupMenu(point) {
menuPopperUpper.popup(desktop, rootMenu.items, point);

View file

@ -2,6 +2,8 @@ import QtQuick 2.0
import QtQuick.Controls 1.4
import Qt.labs.folderlistmodel 2.1
import Qt.labs.settings 1.0
import QtQuick.Controls.Styles 1.4
import QtQuick.Dialogs 1.2 as OriginalDialogs
import ".."
import "../windows"
@ -34,18 +36,26 @@ ModalWindow {
// Set from OffscreenUi::getOpenFile()
property int options; // <-- FIXME unused
property bool selectDirectory: false;
property bool showHidden: false;
// FIXME implement
property bool multiSelect: false;
// FIXME implement
property bool saveDialog: false;
property var helper: fileDialogHelper
property alias model: fileTableView.model
property var drives: helper.drives()
signal selectedFile(var file);
signal canceled();
Component.onCompleted: {
console.log("Helper " + helper + " drives " + drives)
drivesSelector.onCurrentTextChanged.connect(function(){
root.dir = helper.pathToUrl(drivesSelector.currentText);
})
}
Rectangle {
anchors.fill: parent
color: "white"
@ -77,11 +87,22 @@ ModalWindow {
size: 32
onClicked: d.navigateHome();
}
VrControls.ComboBox {
id: drivesSelector
width: 48
height: homeButton.height
model: drives
visible: drives.length > 1
currentIndex: 0
}
}
TextField {
id: currentDirectory
height: homeButton.height
style: TextFieldStyle { renderType: Text.QtRendering }
anchors { left: navControls.right; right: parent.right; top: parent.top; margins: 8 }
property var lastValidFolder: helper.urlToPath(model.folder)
onLastValidFolderChanged: text = lastValidFolder;
@ -122,6 +143,8 @@ ModalWindow {
currentSelectionIsFolder = fileTableView.model.isFolder(row);
if (root.selectDirectory || !currentSelectionIsFolder) {
currentSelection.text = helper.urlToPath(currentSelectionUrl);
} else {
currentSelection.text = ""
}
}
@ -173,17 +196,19 @@ ModalWindow {
if (isFolder) {
fileTableView.model.folder = file
} else {
root.selectedFile(file);
root.destroy();
okAction.trigger();
}
}
}
TextField {
id: currentSelection
style: TextFieldStyle { renderType: Text.QtRendering }
anchors { right: root.selectDirectory ? parent.right : selectionType.left; rightMargin: 8; left: parent.left; leftMargin: 8; top: selectionType.top }
readOnly: true
activeFocusOnTab: false
readOnly: !root.saveDialog
activeFocusOnTab: !readOnly
onActiveFocusChanged: if (activeFocus) { selectAll(); }
onAccepted: okAction.trigger();
}
FileTypeSelection {
@ -203,25 +228,91 @@ ModalWindow {
spacing: 8
Button {
id: openButton
text: root.selectDirectory ? "Choose" : "Open"
enabled: currentSelection.text ? true : false
onClicked: { selectedFile(d.currentSelectionUrl); root.visible = false; }
Keys.onReturnPressed: { selectedFile(d.currentSelectionUrl); root.visible = false; }
action: okAction
Keys.onReturnPressed: okAction.trigger()
KeyNavigation.up: selectionType
KeyNavigation.left: selectionType
KeyNavigation.right: cancelButton
}
Button {
id: cancelButton
text: "Cancel"
action: cancelAction
KeyNavigation.up: selectionType
KeyNavigation.left: openButton
KeyNavigation.right: fileTableView.contentItem
Keys.onReturnPressed: { canceled(); root.enabled = false }
onClicked: { canceled(); root.visible = false; }
}
}
Action {
id: okAction
text: root.saveDialog ? "Save" : (root.selectDirectory ? "Choose" : "Open")
enabled: currentSelection.text ? true : false
onTriggered: okActionTimer.start();
}
Timer {
id: okActionTimer
interval: 50
running: false
repeat: false
onTriggered: {
if (!root.saveDialog) {
selectedFile(d.currentSelectionUrl);
root.destroy()
return;
}
// Handle the ambiguity between different cases
// * typed name (with or without extension)
// * full path vs relative vs filename only
var selection = helper.saveHelper(currentSelection.text, root.dir, selectionType.currentFilter);
if (!selection) {
desktop.messageBox({ icon: OriginalDialogs.StandardIcon.Warning, text: "Unable to parse selection" })
return;
}
if (helper.urlIsDir(selection)) {
root.dir = selection;
currentSelection.text = "";
return;
}
// Check if the file is a valid target
if (!helper.urlIsWritable(selection)) {
desktop.messageBox({
icon: OriginalDialogs.StandardIcon.Warning,
buttons: OriginalDialogs.StandardButton.Yes | OriginalDialogs.StandardButton.No,
text: "Unable to write to location " + selection
})
return;
}
if (helper.urlExists(selection)) {
var messageBox = desktop.messageBox({
icon: OriginalDialogs.StandardIcon.Question,
buttons: OriginalDialogs.StandardButton.Yes | OriginalDialogs.StandardButton.No,
text: "Do you wish to overwrite " + selection + "?",
});
var result = messageBox.exec();
if (OriginalDialogs.StandardButton.Yes !== result) {
return;
}
}
console.log("Selecting " + selection)
selectedFile(selection);
root.destroy();
}
}
Action {
id: cancelAction
text: "Cancel"
onTriggered: { canceled(); root.visible = false; }
}
}
Keys.onPressed: {

View file

@ -25,6 +25,10 @@ ModalWindow {
destroy();
}
function exec() {
return OffscreenUi.waitForMessageBoxResult(root);
}
property alias detailedText: detailedText.text
property alias text: mainTextContainer.text
property alias informativeText: informativeTextContainer.text

View file

@ -62,7 +62,7 @@ ModalWindow {
Item {
anchors { top: mainTextContainer.bottom; bottom: buttons.top; left: parent.left; right: parent.right; margins: d.spacing }
// FIXME make a text field type that can be bound to a history for autocompletion
TextField {
VrControls.TextField {
id: textResult
focus: items ? false : true
visible: items ? false : true

View file

@ -219,7 +219,7 @@ Window {
}
}
TextField {
HifiControls.TextField {
id: filterEdit
anchors.left: parent.left
anchors.right: parent.right
@ -252,7 +252,7 @@ Window {
TableViewColumn { title: "Name"; role: "display"; }
}
TextField {
HifiControls.TextField {
id: selectedScript
readOnly: true
anchors.left: parent.left

View file

@ -17,9 +17,13 @@ VrControls.ComboBox {
onCurrentTextChanged: {
var globRegex = /\((.*)\)$/
var globs = globRegex.exec(currentText);
if (!globs[1]) {
console.warn("Unable to parse filter " + currentText);
return;
if (!globs || !globs[1]) {
globRegex = /^(\*.*)$/
globs = globRegex.exec(currentText);
if (!globs || !globs[1]) {
console.warn("Unable to parse filter " + currentText);
return;
}
}
currentFilter = globs[1].split(" ");
}

View file

@ -1,5 +1,6 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
Preference {
id: root
@ -50,6 +51,7 @@ Preference {
id: dataTextField
placeholderText: root.placeholderText
text: preference.value
style: TextFieldStyle { renderType: Text.QtRendering }
anchors {
top: labelText.bottom
left: parent.left

View file

@ -1,5 +1,6 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import "../../dialogs"
@ -30,6 +31,7 @@ Preference {
id: dataTextField
placeholderText: root.placeholderText
text: preference.value
style: TextFieldStyle { renderType: Text.QtRendering }
anchors {
top: labelText.bottom
left: parent.left

View file

@ -1,5 +1,6 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import "../../controls"
Preference {
id: root

View file

@ -1,5 +1,6 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
Preference {
id: root
@ -24,6 +25,7 @@ Preference {
TextField {
id: dataTextField
placeholderText: preference.placeholderText
style: TextFieldStyle { renderType: Text.QtRendering }
anchors {
top: labelText.bottom
left: parent.left

View file

@ -1,6 +1,7 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.XmlListModel 2.0
import QtQuick.Controls.Styles 1.4
import "../../windows"
import "../../js/Utils.js" as Utils
@ -27,6 +28,7 @@ ModalWindow {
TextField {
id: filterEdit
anchors { left: parent.left; right: parent.right; top: parent.top }
style: TextFieldStyle { renderType: Text.QtRendering }
placeholderText: "filter"
onTextChanged: tableView.model.filter = text
}

View file

@ -1,6 +1,7 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.XmlListModel 2.0
import QtQuick.Controls.Styles 1.4
import "../../windows"
import "../../js/Utils.js" as Utils
@ -26,6 +27,7 @@ ModalWindow {
TextField {
id: filterEdit
style: TextFieldStyle { renderType: Text.QtRendering }
anchors { left: parent.left; right: parent.right; top: parent.top }
placeholderText: "filter"
onTextChanged: tableView.model.filter = text

View file

@ -1,5 +1,6 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import "../../../windows"
import "../../../controls" as VrControls
@ -31,7 +32,7 @@ Item {
height: modelChooserButton.height
anchors { left: parent.left; right: parent.right; }
Text { id: urlLabel; text: "Model URL:"; width: 80; anchors.verticalCenter: modelUrl.verticalCenter }
TextField {
VrControls.TextField {
id: modelUrl;
height: jointChooser.height;
anchors { left: urlLabel.right; leftMargin: 8; rightMargin: 8; right: modelChooserButton.left }

View file

@ -1323,7 +1323,7 @@ void MyAvatar::preRender(RenderArgs* renderArgs) {
_prevShouldDrawHead = shouldDrawHead;
}
const float RENDER_HEAD_CUTOFF_DISTANCE = 0.5f;
const float RENDER_HEAD_CUTOFF_DISTANCE = 0.6f;
bool MyAvatar::cameraInsideHead() const {
const glm::vec3 cameraPosition = qApp->getCamera()->getPosition();
@ -1373,6 +1373,37 @@ void MyAvatar::updateOrientation(float deltaTime) {
totalBodyYaw += _driveKeys[STEP_YAW];
}
// use head/HMD orientation to turn while flying
if (getCharacterController()->getState() == CharacterController::State::Hover) {
// This is the direction the user desires to fly in.
glm::vec3 desiredFacing = getHead()->getCameraOrientation() * Vectors::UNIT_Z;
desiredFacing.y = 0.0f;
// This is our reference frame, it is captured when the user begins to move.
glm::vec3 referenceFacing = transformVector(_sensorToWorldMatrix, _hoverReferenceCameraFacing);
referenceFacing.y = 0.0f;
referenceFacing = glm::normalize(referenceFacing);
glm::vec3 referenceRight(referenceFacing.z, 0.0f, -referenceFacing.x);
const float HOVER_FLY_ROTATION_PERIOD = 0.5f;
float tau = glm::clamp(deltaTime / HOVER_FLY_ROTATION_PERIOD, 0.0f, 1.0f);
// new facing is a linear interpolation between the desired and reference vectors.
glm::vec3 newFacing = glm::normalize((1.0f - tau) * referenceFacing + tau * desiredFacing);
// calcualte the signed delta yaw angle to apply so that we match our newFacing.
float sign = copysignf(1.0f, glm::dot(desiredFacing, referenceRight));
float deltaAngle = sign * acosf(glm::clamp(glm::dot(referenceFacing, newFacing), -1.0f, 1.0f));
// speedFactor is 0 when we are at rest adn 1.0 when we are at max flying speed.
const float MAX_FLYING_SPEED = 30.0f;
float speedFactor = glm::min(glm::length(getVelocity()) / MAX_FLYING_SPEED, 1.0f);
// apply our delta, but scale it by the speed factor, so we turn faster when we are flying faster.
totalBodyYaw += (speedFactor * deltaAngle * (180.0f / PI));
}
// update body orientation by movement inputs
setOrientation(getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f))));
@ -1549,6 +1580,16 @@ void MyAvatar::updatePosition(float deltaTime) {
// update _moving flag based on speed
const float MOVING_SPEED_THRESHOLD = 0.01f;
_moving = speed > MOVING_SPEED_THRESHOLD;
// capture the head rotation, in sensor space, when the user first indicates they would like to move/fly.
if (!_hoverReferenceCameraFacingIsCaptured && (fabs(_driveKeys[TRANSLATE_Z]) > 0.1f || fabs(_driveKeys[TRANSLATE_X]) > 0.1f)) {
_hoverReferenceCameraFacingIsCaptured = true;
// transform the camera facing vector into sensor space.
_hoverReferenceCameraFacing = transformVector(glm::inverse(_sensorToWorldMatrix), getHead()->getCameraOrientation() * Vectors::UNIT_Z);
} else if (_hoverReferenceCameraFacingIsCaptured && (fabs(_driveKeys[TRANSLATE_Z]) <= 0.1f && fabs(_driveKeys[TRANSLATE_X]) <= 0.1f)) {
_hoverReferenceCameraFacingIsCaptured = false;
}
}
void MyAvatar::updateCollisionSound(const glm::vec3 &penetration, float deltaTime, float frequency) {

View file

@ -416,6 +416,8 @@ private:
AtRestDetector _hmdAtRestDetector;
bool _lastIsMoving { false };
bool _hoverReferenceCameraFacingIsCaptured { false };
glm::vec3 _hoverReferenceCameraFacing; // hmd sensor space
float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f };
float AUDIO_ENERGY_CONSTANT { 0.000001f };
@ -429,7 +431,6 @@ private:
float getEnergy();
void setEnergy(float value);
bool didTeleport();
};
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);

View file

@ -9,29 +9,20 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QDir>
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QInputDialog>
#include <QtCore/QDir>
#include <QMessageBox>
#include <QScriptValue>
#include <QScrollArea>
#include "Application.h"
#include "DomainHandler.h"
#include "MainWindow.h"
#include "Menu.h"
#include "OffscreenUi.h"
#include "ui/ModelsBrowser.h"
#include "WebWindowClass.h"
#include "WindowScriptingInterface.h"
WindowScriptingInterface::WindowScriptingInterface() :
_editDialog(NULL),
_nonBlockingFormActive(false),
_formResult(QDialog::Rejected)
{
WindowScriptingInterface::WindowScriptingInterface() {
const DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
connect(&domainHandler, &DomainHandler::connectedToDomain, this, &WindowScriptingInterface::domainChanged);
connect(qApp, &Application::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused);
@ -85,544 +76,31 @@ QScriptValue WindowScriptingInterface::getCursorPositionY() {
return QCursor::pos().y();
}
QScriptValue WindowScriptingInterface::alert(const QString& message) {
QScriptValue retVal;
QMetaObject::invokeMethod(this, "showAlert", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QScriptValue, retVal), Q_ARG(const QString&, message));
return retVal;
}
QScriptValue WindowScriptingInterface::confirm(const QString& message) {
QScriptValue retVal;
QMetaObject::invokeMethod(this, "showConfirm", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, retVal), Q_ARG(const QString&, message));
return retVal;
}
QScriptValue WindowScriptingInterface::form(const QString& title, QScriptValue form) {
QScriptValue retVal;
QMetaObject::invokeMethod(this, "showForm", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, retVal),
Q_ARG(const QString&, title), Q_ARG(QScriptValue, form));
return retVal;
}
QScriptValue WindowScriptingInterface::prompt(const QString& message, const QString& defaultText) {
QScriptValue retVal;
QMetaObject::invokeMethod(this, "showPrompt", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, retVal),
Q_ARG(const QString&, message), Q_ARG(const QString&, defaultText));
return retVal;
}
QScriptValue WindowScriptingInterface::browse(const QString& title, const QString& directory, const QString& nameFilter) {
QScriptValue retVal;
QMetaObject::invokeMethod(this, "showBrowse", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, retVal),
Q_ARG(const QString&, title), Q_ARG(const QString&, directory), Q_ARG(const QString&, nameFilter));
return retVal;
}
QScriptValue WindowScriptingInterface::save(const QString& title, const QString& directory, const QString& nameFilter) {
QScriptValue retVal;
QMetaObject::invokeMethod(this, "showBrowse", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, retVal),
Q_ARG(const QString&, title), Q_ARG(const QString&, directory), Q_ARG(const QString&, nameFilter),
Q_ARG(QFileDialog::AcceptMode, QFileDialog::AcceptSave));
return retVal;
}
QScriptValue WindowScriptingInterface::s3Browse(const QString& nameFilter) {
QScriptValue retVal;
QMetaObject::invokeMethod(this, "showS3Browse", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, retVal),
Q_ARG(const QString&, nameFilter));
return retVal;
}
void WindowScriptingInterface::nonBlockingForm(const QString& title, QScriptValue form) {
QMetaObject::invokeMethod(this, "showNonBlockingForm", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, title), Q_ARG(QScriptValue, form));
}
void WindowScriptingInterface::reloadNonBlockingForm(QScriptValue newValues) {
QMetaObject::invokeMethod(this, "doReloadNonBlockingForm", Qt::BlockingQueuedConnection,
Q_ARG(QScriptValue, newValues));
}
QScriptValue WindowScriptingInterface::getNonBlockingFormResult(QScriptValue form) {
QScriptValue retVal;
QMetaObject::invokeMethod(this, "doGetNonBlockingFormResult", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, retVal),
Q_ARG(QScriptValue, form));
return retVal;
}
QScriptValue WindowScriptingInterface::peekNonBlockingFormResult(QScriptValue form) {
QScriptValue retVal;
QMetaObject::invokeMethod(this, "doPeekNonBlockingFormResult", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, retVal),
Q_ARG(QScriptValue, form));
return retVal;
}
/// Display an alert box
/// \param const QString& message message to display
/// \return QScriptValue::UndefinedValue
QScriptValue WindowScriptingInterface::showAlert(const QString& message) {
void WindowScriptingInterface::alert(const QString& message) {
OffscreenUi::warning("", message);
return QScriptValue::UndefinedValue;
}
/// Display a confirmation box with the options 'Yes' and 'No'
/// \param const QString& message message to display
/// \return QScriptValue `true` if 'Yes' was clicked, `false` otherwise
QScriptValue WindowScriptingInterface::showConfirm(const QString& message) {
bool confirm = false;
if (QMessageBox::Yes == OffscreenUi::question("", message)) {
confirm = true;
}
return QScriptValue(confirm);
}
void WindowScriptingInterface::chooseDirectory() {
QPushButton* button = reinterpret_cast<QPushButton*>(sender());
QString title = button->property("title").toString();
QString path = button->property("path").toString();
QRegExp displayAs = button->property("displayAs").toRegExp();
QRegExp validateAs = button->property("validateAs").toRegExp();
QString errorMessage = button->property("errorMessage").toString();
QString directory = QFileDialog::getExistingDirectory(button, title, path);
if (directory.isEmpty()) {
return;
}
if (!validateAs.exactMatch(directory)) {
OffscreenUi::warning(NULL, "Invalid Directory", errorMessage);
return;
}
button->setProperty("path", directory);
displayAs.indexIn(directory);
QString buttonText = displayAs.cap(1) != "" ? displayAs.cap(1) : ".";
button->setText(buttonText);
}
void WindowScriptingInterface::inlineButtonClicked() {
QPushButton* button = reinterpret_cast<QPushButton*>(sender());
QString name = button->property("name").toString();
emit inlineButtonClicked(name);
}
QString WindowScriptingInterface::jsRegExp2QtRegExp(QString string) {
// Converts string representation of RegExp from JavaScript format to Qt format.
return string.mid(1, string.length() - 2) // No enclosing slashes.
.replace("\\/", "/"); // No escaping of forward slash.
}
void WindowScriptingInterface::showNonBlockingForm(const QString& title, QScriptValue form) {
if (!form.isArray() || (form.isArray() && form.property("length").toInt32() <= 0)) {
return;
}
// what should we do if someone calls us while we still think we have a dialog showing???
if (_nonBlockingFormActive) {
qDebug() << "Show Non-Blocking Form called when form already active.";
return;
}
_form = form;
_editDialog = createForm(title, _form);
_nonBlockingFormActive = true;
connect(_editDialog, SIGNAL(accepted()), this, SLOT(nonBlockingFormAccepted()));
connect(_editDialog, SIGNAL(rejected()), this, SLOT(nonBlockingFormRejected()));
_editDialog->setModal(true);
_editDialog->show();
}
void WindowScriptingInterface::doReloadNonBlockingForm(QScriptValue newValues) {
if (!newValues.isArray() || (newValues.isArray() && newValues.property("length").toInt32() <= 0)) {
return;
}
// what should we do if someone calls us while we still think we have a dialog showing???
if (!_editDialog) {
qDebug() << "Reload Non-Blocking Form called when no form is active.";
return;
}
for (int i = 0; i < newValues.property("length").toInt32(); ++i) {
QScriptValue item = newValues.property(i);
if (item.property("oldIndex").isValid()) {
int oldIndex = item.property("oldIndex").toInt32();
QScriptValue oldItem = _form.property(oldIndex);
if (oldItem.isValid()) {
QLineEdit* originalEdit = _edits[oldItem.property("editIndex").toInt32()];
originalEdit->setText(item.property("value").toString());
}
}
}
}
bool WindowScriptingInterface::nonBlockingFormActive() {
return _nonBlockingFormActive;
}
QScriptValue WindowScriptingInterface::doPeekNonBlockingFormResult(QScriptValue array) {
QScriptValue retVal;
int e = -1;
int d = -1;
int c = -1;
int h = -1;
for (int i = 0; i < _form.property("length").toInt32(); ++i) {
QScriptValue item = _form.property(i);
QScriptValue value = item.property("value");
if (item.property("button").toString() != "") {
// Nothing to do
} else if (item.property("type").toString() == "inlineButton") {
// Nothing to do
} else if (item.property("type").toString() == "header") {
// Nothing to do
} else if (item.property("directory").toString() != "") {
d += 1;
value = _directories.at(d)->property("path").toString();
item.setProperty("directory", value);
_form.setProperty(i, item);
} else if (item.property("options").isArray()) {
c += 1;
item.setProperty("value",
_combos.at(c)->currentIndex() < item.property("options").property("length").toInt32() ?
item.property("options").property(_combos.at(c)->currentIndex()) :
array.engine()->undefinedValue()
);
_form.setProperty(i, item);
} else if (item.property("type").toString() == "checkbox") {
h++;
value = _checks.at(h)->checkState() == Qt::Checked;
item.setProperty("value", value);
_form.setProperty(i, item);
} else {
e += 1;
bool ok = true;
if (value.isNumber()) {
value = _edits.at(e)->text().toDouble(&ok);
} else if (value.isString()) {
value = _edits.at(e)->text();
} else if (value.isBool()) {
if (_edits.at(e)->text() == "true") {
value = true;
} else if (_edits.at(e)->text() == "false") {
value = false;
} else {
ok = false;
}
}
if (ok) {
item.setProperty("value", value);
_form.setProperty(i, item);
}
}
}
array = _form;
return (_formResult == QDialog::Accepted);
}
QScriptValue WindowScriptingInterface::doGetNonBlockingFormResult(QScriptValue array) {
QScriptValue retVal;
if (_formResult == QDialog::Accepted) {
int e = -1;
int d = -1;
int c = -1;
int h = -1;
for (int i = 0; i < _form.property("length").toInt32(); ++i) {
QScriptValue item = _form.property(i);
QScriptValue value = item.property("value");
if (item.property("button").toString() != "") {
// Nothing to do
} else if (item.property("type").toString() == "inlineButton") {
// Nothing to do
} else if (item.property("type").toString() == "header") {
// Nothing to do
} else if (item.property("directory").toString() != "") {
d += 1;
value = _directories.at(d)->property("path").toString();
item.setProperty("directory", value);
_form.setProperty(i, item);
} else if (item.property("options").isArray()) {
c += 1;
item.setProperty("value",
_combos.at(c)->currentIndex() < item.property("options").property("length").toInt32() ?
item.property("options").property(_combos.at(c)->currentIndex()) :
array.engine()->undefinedValue()
);
_form.setProperty(i, item);
} else if (item.property("type").toString() == "checkbox") {
h++;
value = _checks.at(h)->checkState() == Qt::Checked;
item.setProperty("value", value);
_form.setProperty(i, item);
} else {
e += 1;
bool ok = true;
if (value.isNumber()) {
value = _edits.at(e)->text().toDouble(&ok);
} else if (value.isString()) {
value = _edits.at(e)->text();
} else if (value.isBool()) {
if (_edits.at(e)->text() == "true") {
value = true;
} else if (_edits.at(e)->text() == "false") {
value = false;
} else {
ok = false;
}
}
if (ok) {
item.setProperty("value", value);
_form.setProperty(i, item);
}
}
}
}
delete _editDialog;
_editDialog = NULL;
_form = QScriptValue();
_edits.clear();
_directories.clear();
_combos.clear();
_checks.clear();
array = _form;
return (_formResult == QDialog::Accepted);
}
/// Display a form layout with an edit box
/// \param const QString& title title to display
/// \param const QScriptValue form to display as an array of objects:
/// - label, value
/// - label, directory, title, display regexp, validate regexp, error message
/// - button ("Cancel")
/// \return QScriptValue `true` if 'OK' was clicked, `false` otherwise
QScriptValue WindowScriptingInterface::showForm(const QString& title, QScriptValue form) {
if (form.isArray() && form.property("length").toInt32() <= 0) {
return false;
}
QDialog* editDialog = createForm(title, form);
int result = editDialog->exec();
if (result == QDialog::Accepted) {
int e = -1;
int d = -1;
int c = -1;
int h = -1;
for (int i = 0; i < form.property("length").toInt32(); ++i) {
QScriptValue item = form.property(i);
QScriptValue value = item.property("value");
if (item.property("button").toString() != "") {
// Nothing to do
} else if (item.property("type").toString() == "inlineButton") {
// Nothing to do
} else if (item.property("type").toString() == "header") {
// Nothing to do
} else if (item.property("directory").toString() != "") {
d += 1;
value = _directories.at(d)->property("path").toString();
item.setProperty("directory", value);
form.setProperty(i, item);
} else if (item.property("options").isArray()) {
c += 1;
item.setProperty("value",
_combos.at(c)->currentIndex() < item.property("options").property("length").toInt32() ?
item.property("options").property(_combos.at(c)->currentIndex()) :
form.engine()->undefinedValue()
);
form.setProperty(i, item);
} else if (item.property("type").toString() == "checkbox") {
h++;
value = _checks.at(h)->checkState() == Qt::Checked;
item.setProperty("value", value);
form.setProperty(i, item);
} else {
e += 1;
bool ok = true;
if (value.isNumber()) {
value = _edits.at(e)->text().toDouble(&ok);
} else if (value.isString()) {
value = _edits.at(e)->text();
} else if (value.isBool()) {
if (_edits.at(e)->text() == "true") {
value = true;
} else if (_edits.at(e)->text() == "false") {
value = false;
} else {
ok = false;
}
}
if (ok) {
item.setProperty("value", value);
form.setProperty(i, item);
}
}
}
}
delete editDialog;
_combos.clear();
_checks.clear();
_edits.clear();
_directories.clear();
return (result == QDialog::Accepted);
}
QDialog* WindowScriptingInterface::createForm(const QString& title, QScriptValue form) {
QDialog* editDialog = new QDialog(qApp->getWindow());
editDialog->setWindowTitle(title);
bool cancelButton = false;
QVBoxLayout* layout = new QVBoxLayout();
editDialog->setLayout(layout);
QScrollArea* area = new QScrollArea();
layout->addWidget(area);
area->setWidgetResizable(true);
QWidget* container = new QWidget();
QFormLayout* formLayout = new QFormLayout();
container->setLayout(formLayout);
container->sizePolicy().setHorizontalStretch(1);
formLayout->setRowWrapPolicy(QFormLayout::DontWrapRows);
formLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
formLayout->setFormAlignment(Qt::AlignHCenter | Qt::AlignTop);
formLayout->setLabelAlignment(Qt::AlignLeft);
area->setWidget(container);
for (int i = 0; i < form.property("length").toInt32(); ++i) {
QScriptValue item = form.property(i);
if (item.property("button").toString() != "") {
cancelButton = cancelButton || item.property("button").toString().toLower() == "cancel";
} else if (item.property("directory").toString() != "") {
QString path = item.property("directory").toString();
QString title = item.property("title").toString();
if (title == "") {
title = "Choose Directory";
}
QString displayAsString = item.property("displayAs").toString();
QRegExp displayAs = QRegExp(displayAsString != "" ? jsRegExp2QtRegExp(displayAsString) : "^(.*)$");
QString validateAsString = item.property("validateAs").toString();
QRegExp validateAs = QRegExp(validateAsString != "" ? jsRegExp2QtRegExp(validateAsString) : ".*");
QString errorMessage = item.property("errorMessage").toString();
if (errorMessage == "") {
errorMessage = "Invalid directory";
}
QPushButton* directory = new QPushButton(displayAs.cap(1));
directory->setProperty("title", title);
directory->setProperty("path", path);
directory->setProperty("displayAs", displayAs);
directory->setProperty("validateAs", validateAs);
directory->setProperty("errorMessage", errorMessage);
displayAs.indexIn(path);
directory->setText(displayAs.cap(1) != "" ? displayAs.cap(1) : ".");
directory->setMinimumWidth(200);
_directories.push_back(directory);
formLayout->addRow(new QLabel(item.property("label").toString()), directory);
connect(directory, SIGNAL(clicked(bool)), SLOT(chooseDirectory()));
} else if (item.property("type").toString() == "inlineButton") {
QString buttonLabel = item.property("buttonLabel").toString();
QPushButton* inlineButton = new QPushButton(buttonLabel);
inlineButton->setMinimumWidth(200);
inlineButton->setProperty("name", item.property("name").toString());
formLayout->addRow(new QLabel(item.property("label").toString()), inlineButton);
connect(inlineButton, SIGNAL(clicked(bool)), SLOT(inlineButtonClicked()));
} else if (item.property("type").toString() == "header") {
formLayout->addRow(new QLabel(item.property("label").toString()));
} else if (item.property("options").isArray()) {
QComboBox* combo = new QComboBox();
combo->setMinimumWidth(200);
qint32 options_count = item.property("options").property("length").toInt32();
for (qint32 i = 0; i < options_count; i++) {
combo->addItem(item.property("options").property(i).toString());
}
_combos.push_back(combo);
formLayout->addRow(new QLabel(item.property("label").toString()), combo);
} else if (item.property("type").toString() == "checkbox") {
QCheckBox* check = new QCheckBox();
check->setTristate(false);
check->setCheckState(item.property("value").toString() == "true" ? Qt::Checked : Qt::Unchecked);
_checks.push_back(check);
formLayout->addRow(new QLabel(item.property("label").toString()), check);
} else {
QLineEdit* edit = new QLineEdit(item.property("value").toString());
edit->setMinimumWidth(200);
int editIndex = _edits.size();
_edits.push_back(edit);
item.setProperty("editIndex", editIndex);
formLayout->addRow(new QLabel(item.property("label").toString()), edit);
}
}
QDialogButtonBox* buttons = new QDialogButtonBox(
QDialogButtonBox::Ok
| (cancelButton ? QDialogButtonBox::Cancel : QDialogButtonBox::NoButton)
);
connect(buttons, SIGNAL(accepted()), editDialog, SLOT(accept()));
connect(buttons, SIGNAL(rejected()), editDialog, SLOT(reject()));
layout->addWidget(buttons);
return editDialog;
QScriptValue WindowScriptingInterface::confirm(const QString& message) {
return QScriptValue((QMessageBox::Yes == OffscreenUi::question("", message)));
}
/// Display a prompt with a text box
/// \param const QString& message message to display
/// \param const QString& defaultText default text in the text box
/// \return QScriptValue string text value in text box if the dialog was accepted, `null` otherwise.
QScriptValue WindowScriptingInterface::showPrompt(const QString& message, const QString& defaultText) {
QInputDialog promptDialog(qApp->getWindow());
promptDialog.setWindowTitle("");
promptDialog.setLabelText(message);
promptDialog.setTextValue(defaultText);
promptDialog.setFixedSize(600, 200);
if (promptDialog.exec() == QDialog::Accepted) {
return QScriptValue(promptDialog.textValue());
}
return QScriptValue::NullValue;
QScriptValue WindowScriptingInterface::prompt(const QString& message, const QString& defaultText) {
bool ok = false;
QString result = OffscreenUi::getText(nullptr, "", message, QLineEdit::Normal, defaultText, &ok);
return ok ? QScriptValue(result) : QScriptValue::NullValue;
}
/// Display a file dialog. If `directory` is an invalid file or directory the browser will start at the current
/// working directory.
/// \param const QString& title title of the window
/// \param const QString& directory directory to start the file browser at
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue`
QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QString& directory, const QString& nameFilter,
QFileDialog::AcceptMode acceptMode) {
QString fixupPathForMac(const QString& directory) {
// On OS X `directory` does not work as expected unless a file is included in the path, so we append a bogus
// filename if the directory is valid.
QString path = "";
@ -631,35 +109,31 @@ QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QS
fileInfo.setFile(directory, "__HIFI_INVALID_FILE__");
path = fileInfo.filePath();
}
QFileDialog fileDialog(qApp->getWindow(), title, path, nameFilter);
fileDialog.setAcceptMode(acceptMode);
QUrl fileUrl(directory);
if (acceptMode == QFileDialog::AcceptSave) {
// TODO -- Setting this breaks the dialog on Linux. Does it help something on other platforms?
// fileDialog.setFileMode(QFileDialog::Directory);
fileDialog.selectFile(fileUrl.fileName());
}
if (fileDialog.exec()) {
return QScriptValue(fileDialog.selectedFiles().first());
}
return QScriptValue::NullValue;
return path;
}
/// Display a browse window for S3 models
/// \param const QString& nameFilter filter to filter filenames
/// Display an open file dialog. If `directory` is an invalid file or directory the browser will start at the current
/// working directory.
/// \param const QString& title title of the window
/// \param const QString& directory directory to start the file browser at
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue`
QScriptValue WindowScriptingInterface::showS3Browse(const QString& nameFilter) {
ModelsBrowser browser(FSTReader::ENTITY_MODEL);
if (nameFilter != "") {
browser.setNameFilter(nameFilter);
}
QEventLoop loop;
connect(&browser, &ModelsBrowser::selected, &loop, &QEventLoop::quit);
QMetaObject::invokeMethod(&browser, "browse", Qt::QueuedConnection);
loop.exec();
return browser.getSelectedFile();
QScriptValue WindowScriptingInterface::browse(const QString& title, const QString& directory, const QString& nameFilter) {
QString path = fixupPathForMac(directory);
QString result = OffscreenUi::getOpenFileName(nullptr, title, path, nameFilter);
return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result);
}
/// Display a save file dialog. If `directory` is an invalid file or directory the browser will start at the current
/// working directory.
/// \param const QString& title title of the window
/// \param const QString& directory directory to start the file browser at
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue`
QScriptValue WindowScriptingInterface::save(const QString& title, const QString& directory, const QString& nameFilter) {
QString path = fixupPathForMac(directory);
QString result = OffscreenUi::getSaveFileName(nullptr, title, path, nameFilter);
return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result);
}
int WindowScriptingInterface::getInnerWidth() {

View file

@ -12,12 +12,9 @@
#ifndef hifi_WindowScriptingInterface_h
#define hifi_WindowScriptingInterface_h
#include <QObject>
#include <QScriptValue>
#include <QString>
#include <QFileDialog>
#include <QComboBox>
#include <QLineEdit>
#include <QtCore/QObject>
#include <QtCore/QString>
#include <QtScript/QScriptValue>
class WebWindowClass;
@ -41,61 +38,19 @@ public slots:
QScriptValue hasFocus();
void setFocus();
void raiseMainWindow();
QScriptValue alert(const QString& message = "");
void alert(const QString& message = "");
QScriptValue confirm(const QString& message = "");
QScriptValue form(const QString& title, QScriptValue array);
QScriptValue prompt(const QString& message = "", const QString& defaultText = "");
QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
QScriptValue s3Browse(const QString& nameFilter = "");
void nonBlockingForm(const QString& title, QScriptValue array);
void reloadNonBlockingForm(QScriptValue array);
QScriptValue getNonBlockingFormResult(QScriptValue array);
QScriptValue peekNonBlockingFormResult(QScriptValue array);
signals:
void domainChanged(const QString& domainHostname);
void inlineButtonClicked(const QString& name);
void nonBlockingFormClosed();
void svoImportRequested(const QString& url);
void domainConnectionRefused(const QString& reason);
private slots:
QScriptValue showAlert(const QString& message);
QScriptValue showConfirm(const QString& message);
QScriptValue showForm(const QString& title, QScriptValue form);
QScriptValue showPrompt(const QString& message, const QString& defaultText);
QScriptValue showBrowse(const QString& title, const QString& directory, const QString& nameFilter,
QFileDialog::AcceptMode acceptMode = QFileDialog::AcceptOpen);
QScriptValue showS3Browse(const QString& nameFilter);
void showNonBlockingForm(const QString& title, QScriptValue array);
void doReloadNonBlockingForm(QScriptValue array);
bool nonBlockingFormActive();
QScriptValue doGetNonBlockingFormResult(QScriptValue array);
QScriptValue doPeekNonBlockingFormResult(QScriptValue array);
void chooseDirectory();
void inlineButtonClicked();
void nonBlockingFormAccepted() { _nonBlockingFormActive = false; _formResult = QDialog::Accepted; emit nonBlockingFormClosed(); }
void nonBlockingFormRejected() { _nonBlockingFormActive = false; _formResult = QDialog::Rejected; emit nonBlockingFormClosed(); }
WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height);
private:
QString jsRegExp2QtRegExp(QString string);
QDialog* createForm(const QString& title, QScriptValue form);
QDialog* _editDialog;
QScriptValue _form;
bool _nonBlockingFormActive;
int _formResult;
QVector<QComboBox*> _combos;
QVector<QCheckBox*> _checks;
QVector<QLineEdit*> _edits;
QVector<QPushButton*> _directories;
};
#endif // hifi_WindowScriptingInterface_h

View file

@ -12,6 +12,8 @@
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QDebug>
#include <QtCore/QRegularExpression>
QUrl FileDialogHelper::home() {
@ -26,7 +28,11 @@ QString FileDialogHelper::urlToPath(const QUrl& url) {
return url.toLocalFile();
}
bool FileDialogHelper::validPath(const QString& path) {
bool FileDialogHelper::fileExists(const QString& path) {
return QFile(path).exists();
}
bool FileDialogHelper::validPath(const QString& path) {
return QFile(path).exists();
}
@ -38,3 +44,62 @@ QUrl FileDialogHelper::pathToUrl(const QString& path) {
return QUrl::fromLocalFile(path);
}
QUrl FileDialogHelper::saveHelper(const QString& saveText, const QUrl& currentFolder, const QStringList& selectionFilters) {
qDebug() << "Calling save helper with " << saveText << " " << currentFolder << " " << selectionFilters;
QFileInfo fileInfo(saveText);
// Check if it's a relative path and if it is resolve to the absolute path
{
if (fileInfo.isRelative()) {
fileInfo = QFileInfo(currentFolder.toLocalFile() + "/" + fileInfo.filePath());
}
}
// Check if we need to append an extension, but only if the current resolved path isn't a directory
if (!fileInfo.isDir()) {
QString fileName = fileInfo.fileName();
if (!fileName.contains(".") && selectionFilters.size() == 1) {
const QRegularExpression extensionRe{ ".*(\\.[a-zA-Z0-9]+)$" };
QString filter = selectionFilters[0];
auto match = extensionRe.match(filter);
if (match.hasMatch()) {
fileInfo = QFileInfo(fileInfo.filePath() + match.captured(1));
}
}
}
return QUrl::fromLocalFile(fileInfo.absoluteFilePath());
}
bool FileDialogHelper::urlIsDir(const QUrl& url) {
return QFileInfo(url.toLocalFile()).isDir();
}
bool FileDialogHelper::urlIsFile(const QUrl& url) {
return QFileInfo(url.toLocalFile()).isFile();
}
bool FileDialogHelper::urlExists(const QUrl& url) {
return QFileInfo(url.toLocalFile()).exists();
}
bool FileDialogHelper::urlIsWritable(const QUrl& url) {
QFileInfo fileInfo(url.toLocalFile());
// Is the file writable?
if (fileInfo.exists()) {
return fileInfo.isWritable();
}
// No file, get the parent directory and check if writable
return QFileInfo(fileInfo.absoluteDir().absolutePath()).isWritable();
}
QStringList FileDialogHelper::drives() {
QStringList result;
for (const auto& drive : QDir::drives()) {
result << drive.absolutePath();
}
return result;
}

View file

@ -47,10 +47,17 @@ public:
Q_INVOKABLE QUrl home();
Q_INVOKABLE QStringList standardPath(StandardLocation location);
Q_INVOKABLE QStringList drives();
Q_INVOKABLE QString urlToPath(const QUrl& url);
Q_INVOKABLE bool urlIsDir(const QUrl& url);
Q_INVOKABLE bool urlIsFile(const QUrl& url);
Q_INVOKABLE bool urlExists(const QUrl& url);
Q_INVOKABLE bool urlIsWritable(const QUrl& url);
Q_INVOKABLE bool fileExists(const QString& path);
Q_INVOKABLE bool validPath(const QString& path);
Q_INVOKABLE bool validFolder(const QString& path);
Q_INVOKABLE QUrl pathToUrl(const QString& path);
Q_INVOKABLE QUrl saveHelper(const QString& saveText, const QUrl& currentFolder, const QStringList& selectionFilters);
};

View file

@ -105,6 +105,7 @@ void OffscreenUi::create(QOpenGLContext* context) {
OffscreenQmlSurface::create(context);
auto rootContext = getRootContext();
rootContext->setContextProperty("OffscreenUi", this);
rootContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags());
rootContext->setContextProperty("urlHandler", new UrlHandler());
rootContext->setContextProperty("fileDialogHelper", new FileDialogHelper());
@ -219,7 +220,7 @@ QQuickItem* OffscreenUi::createMessageBox(QMessageBox::Icon icon, const QString&
return qvariant_cast<QQuickItem*>(result);
}
QMessageBox::StandardButton OffscreenUi::waitForMessageBoxResult(QQuickItem* messageBox) {
int OffscreenUi::waitForMessageBoxResult(QQuickItem* messageBox) {
if (!messageBox) {
return QMessageBox::NoButton;
}
@ -241,7 +242,7 @@ QMessageBox::StandardButton OffscreenUi::messageBox(QMessageBox::Icon icon, cons
return result;
}
return waitForMessageBoxResult(createMessageBox(icon, title, text, buttons, defaultButton));
return static_cast<QMessageBox::StandardButton>(waitForMessageBoxResult(createMessageBox(icon, title, text, buttons, defaultButton)));
}
QMessageBox::StandardButton OffscreenUi::critical(const QString& title, const QString& text,
@ -478,6 +479,26 @@ private slots:
}
};
QString OffscreenUi::fileDialog(const QVariantMap& properties) {
QVariant buildDialogResult;
bool invokeResult = QMetaObject::invokeMethod(_desktop, "fileDialog",
Q_RETURN_ARG(QVariant, buildDialogResult),
Q_ARG(QVariant, QVariant::fromValue(properties)));
if (!invokeResult) {
qWarning() << "Failed to create file open dialog";
return QString();
}
QVariant result = FileDialogListener(qvariant_cast<QQuickItem*>(buildDialogResult)).waitForResult();
if (!result.isValid()) {
return QString();
}
qDebug() << result.toString();
return result.toUrl().toLocalFile();
}
QString OffscreenUi::fileOpenDialog(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
if (QThread::currentThread() != thread()) {
QString result;
@ -497,28 +518,40 @@ QString OffscreenUi::fileOpenDialog(const QString& caption, const QString& dir,
map.insert("dir", QUrl::fromLocalFile(dir));
map.insert("filter", filter);
map.insert("options", static_cast<int>(options));
return fileDialog(map);
}
QVariant buildDialogResult;
bool invokeResult = QMetaObject::invokeMethod(_desktop, "fileOpenDialog",
Q_RETURN_ARG(QVariant, buildDialogResult),
Q_ARG(QVariant, QVariant::fromValue(map)));
if (!invokeResult) {
qWarning() << "Failed to create file open dialog";
return QString();
QString OffscreenUi::fileSaveDialog(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
if (QThread::currentThread() != thread()) {
QString result;
QMetaObject::invokeMethod(this, "fileSaveDialog", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QString, result),
Q_ARG(QString, caption),
Q_ARG(QString, dir),
Q_ARG(QString, filter),
Q_ARG(QString*, selectedFilter),
Q_ARG(QFileDialog::Options, options));
return result;
}
QVariant result = FileDialogListener(qvariant_cast<QQuickItem*>(buildDialogResult)).waitForResult();
if (!result.isValid()) {
return QString();
}
qDebug() << result.toString();
return result.toUrl().toLocalFile();
// FIXME support returning the selected filter... somehow?
QVariantMap map;
map.insert("caption", caption);
map.insert("dir", QUrl::fromLocalFile(dir));
map.insert("filter", filter);
map.insert("options", static_cast<int>(options));
map.insert("saveDialog", true);
return fileDialog(map);
}
QString OffscreenUi::getOpenFileName(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
return DependencyManager::get<OffscreenUi>()->fileOpenDialog(caption, dir, filter, selectedFilter, options);
}
QString OffscreenUi::getSaveFileName(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
return DependencyManager::get<OffscreenUi>()->fileSaveDialog(caption, dir, filter, selectedFilter, options);
}
#include "OffscreenUi.moc"

View file

@ -47,7 +47,7 @@ public:
// Must be called from the main thread
QQuickItem* createMessageBox(QMessageBox::Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton);
// Must be called from the main thread
QMessageBox::StandardButton waitForMessageBoxResult(QQuickItem* messageBox);
Q_INVOKABLE int waitForMessageBoxResult(QQuickItem* messageBox);
/// Same design as QMessageBox::critical(), will block, returns result
static QMessageBox::StandardButton critical(void* ignored, const QString& title, const QString& text,
@ -87,10 +87,13 @@ public:
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
// file dialog compatibility
Q_INVOKABLE QString fileOpenDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE QString fileSaveDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
// Compatibility with QFileDialog::getOpenFileName
static QString getOpenFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
// Compatibility with QFileDialog::getSaveFileName
static QString getSaveFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
// input dialog compatibility
@ -104,6 +107,8 @@ public:
static QString getItem(void *ignored, const QString & title, const QString & label, const QStringList & items, int current = 0, bool editable = true, bool * ok = 0, Qt::WindowFlags flags = 0, Qt::InputMethodHints inputMethodHints = Qt::ImhNone);
private:
QString fileDialog(const QVariantMap& properties);
QQuickItem* _desktop { nullptr };
QQuickItem* _toolWindow { nullptr };
};

View file

@ -118,13 +118,7 @@ ApplicationWindow {
Button {
text: "Open File"
property var builder: Component {
FileDialog {
folder: "file:///C:/users/bdavis";
filterModel: ListModel {
ListElement { text: "Javascript Files (*.js)"; filter: "*.js" }
ListElement { text: "All Files (*.*)"; filter: "*.*" }
}
}
FileDialog { }
}
onClicked: {