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

This commit is contained in:
samcake 2016-02-05 09:23:31 -08:00
commit 57b6ad3c54
30 changed files with 369 additions and 1131 deletions

View file

@ -23,7 +23,6 @@ Script.include([
"libraries/ToolTip.js", "libraries/ToolTip.js",
"libraries/entityPropertyDialogBox.js",
"libraries/entityCameraTool.js", "libraries/entityCameraTool.js",
"libraries/gridTool.js", "libraries/gridTool.js",
"libraries/entityList.js", "libraries/entityList.js",
@ -32,7 +31,6 @@ Script.include([
var selectionDisplay = SelectionDisplay; var selectionDisplay = SelectionDisplay;
var selectionManager = SelectionManager; var selectionManager = SelectionManager;
var entityPropertyDialogBox = EntityPropertyDialogBox;
var lightOverlayManager = new LightOverlayManager(); 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 { label: Text {
text: control.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 { } } Component { id: fileDialogBuilder; FileDialog { } }
function fileOpenDialog(properties) { function fileDialog(properties) {
return fileDialogBuilder.createObject(desktop, properties); return fileDialogBuilder.createObject(desktop, properties);
} }
MenuMouseHandler { id: menuPopperUpper } MenuMouseHandler { id: menuPopperUpper }
function popupMenu(point) { function popupMenu(point) {
menuPopperUpper.popup(desktop, rootMenu.items, point); menuPopperUpper.popup(desktop, rootMenu.items, point);

View file

@ -2,6 +2,8 @@ import QtQuick 2.0
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import Qt.labs.folderlistmodel 2.1 import Qt.labs.folderlistmodel 2.1
import Qt.labs.settings 1.0 import Qt.labs.settings 1.0
import QtQuick.Controls.Styles 1.4
import QtQuick.Dialogs 1.2 as OriginalDialogs
import ".." import ".."
import "../windows" import "../windows"
@ -34,18 +36,26 @@ ModalWindow {
// Set from OffscreenUi::getOpenFile() // Set from OffscreenUi::getOpenFile()
property int options; // <-- FIXME unused property int options; // <-- FIXME unused
property bool selectDirectory: false; property bool selectDirectory: false;
property bool showHidden: false; property bool showHidden: false;
// FIXME implement // FIXME implement
property bool multiSelect: false; property bool multiSelect: false;
// FIXME implement
property bool saveDialog: false; property bool saveDialog: false;
property var helper: fileDialogHelper property var helper: fileDialogHelper
property alias model: fileTableView.model property alias model: fileTableView.model
property var drives: helper.drives()
signal selectedFile(var file); signal selectedFile(var file);
signal canceled(); signal canceled();
Component.onCompleted: {
console.log("Helper " + helper + " drives " + drives)
drivesSelector.onCurrentTextChanged.connect(function(){
root.dir = helper.pathToUrl(drivesSelector.currentText);
})
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: "white" color: "white"
@ -77,11 +87,22 @@ ModalWindow {
size: 32 size: 32
onClicked: d.navigateHome(); onClicked: d.navigateHome();
} }
VrControls.ComboBox {
id: drivesSelector
width: 48
height: homeButton.height
model: drives
visible: drives.length > 1
currentIndex: 0
}
} }
TextField { TextField {
id: currentDirectory id: currentDirectory
height: homeButton.height height: homeButton.height
style: TextFieldStyle { renderType: Text.QtRendering }
anchors { left: navControls.right; right: parent.right; top: parent.top; margins: 8 } anchors { left: navControls.right; right: parent.right; top: parent.top; margins: 8 }
property var lastValidFolder: helper.urlToPath(model.folder) property var lastValidFolder: helper.urlToPath(model.folder)
onLastValidFolderChanged: text = lastValidFolder; onLastValidFolderChanged: text = lastValidFolder;
@ -122,6 +143,8 @@ ModalWindow {
currentSelectionIsFolder = fileTableView.model.isFolder(row); currentSelectionIsFolder = fileTableView.model.isFolder(row);
if (root.selectDirectory || !currentSelectionIsFolder) { if (root.selectDirectory || !currentSelectionIsFolder) {
currentSelection.text = helper.urlToPath(currentSelectionUrl); currentSelection.text = helper.urlToPath(currentSelectionUrl);
} else {
currentSelection.text = ""
} }
} }
@ -173,17 +196,19 @@ ModalWindow {
if (isFolder) { if (isFolder) {
fileTableView.model.folder = file fileTableView.model.folder = file
} else { } else {
root.selectedFile(file); okAction.trigger();
root.destroy();
} }
} }
} }
TextField { TextField {
id: currentSelection 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 } anchors { right: root.selectDirectory ? parent.right : selectionType.left; rightMargin: 8; left: parent.left; leftMargin: 8; top: selectionType.top }
readOnly: true readOnly: !root.saveDialog
activeFocusOnTab: false activeFocusOnTab: !readOnly
onActiveFocusChanged: if (activeFocus) { selectAll(); }
onAccepted: okAction.trigger();
} }
FileTypeSelection { FileTypeSelection {
@ -203,25 +228,91 @@ ModalWindow {
spacing: 8 spacing: 8
Button { Button {
id: openButton id: openButton
text: root.selectDirectory ? "Choose" : "Open" action: okAction
enabled: currentSelection.text ? true : false Keys.onReturnPressed: okAction.trigger()
onClicked: { selectedFile(d.currentSelectionUrl); root.visible = false; }
Keys.onReturnPressed: { selectedFile(d.currentSelectionUrl); root.visible = false; }
KeyNavigation.up: selectionType KeyNavigation.up: selectionType
KeyNavigation.left: selectionType KeyNavigation.left: selectionType
KeyNavigation.right: cancelButton KeyNavigation.right: cancelButton
} }
Button { Button {
id: cancelButton id: cancelButton
text: "Cancel" action: cancelAction
KeyNavigation.up: selectionType KeyNavigation.up: selectionType
KeyNavigation.left: openButton KeyNavigation.left: openButton
KeyNavigation.right: fileTableView.contentItem KeyNavigation.right: fileTableView.contentItem
Keys.onReturnPressed: { canceled(); root.enabled = false } 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: { Keys.onPressed: {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -268,17 +268,6 @@ QVector<AvatarManager::LocalLight> AvatarManager::getLocalLights() const {
return _localLights; return _localLights;
} }
QVector<QUuid> AvatarManager::getAvatarIdentifiers() {
QReadLocker locker(&_hashLock);
return _avatarHash.keys().toVector();
}
AvatarData* AvatarManager::getAvatar(QUuid avatarID) {
// Null/Default-constructed QUuids will return MyAvatar
return getAvatarBySessionID(avatarID).get();
}
void AvatarManager::getObjectsToRemoveFromPhysics(VectorOfMotionStates& result) { void AvatarManager::getObjectsToRemoveFromPhysics(VectorOfMotionStates& result) {
result.clear(); result.clear();
result.swap(_motionStatesToRemoveFromPhysics); result.swap(_motionStatesToRemoveFromPhysics);

View file

@ -39,7 +39,7 @@ public:
void init(); void init();
MyAvatar* getMyAvatar() { return _myAvatar.get(); } MyAvatar* getMyAvatar() { return _myAvatar.get(); }
AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID); AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID) override;
void updateMyAvatar(float deltaTime); void updateMyAvatar(float deltaTime);
void updateOtherAvatars(float deltaTime); void updateOtherAvatars(float deltaTime);
@ -56,9 +56,6 @@ public:
Q_INVOKABLE void setLocalLights(const QVector<AvatarManager::LocalLight>& localLights); Q_INVOKABLE void setLocalLights(const QVector<AvatarManager::LocalLight>& localLights);
Q_INVOKABLE QVector<AvatarManager::LocalLight> getLocalLights() const; Q_INVOKABLE QVector<AvatarManager::LocalLight> getLocalLights() const;
// Currently, your own avatar will be included as the null avatar id.
Q_INVOKABLE QVector<QUuid> getAvatarIdentifiers();
Q_INVOKABLE AvatarData* getAvatar(QUuid avatarID);
void getObjectsToRemoveFromPhysics(VectorOfMotionStates& motionStates); void getObjectsToRemoveFromPhysics(VectorOfMotionStates& motionStates);

View file

@ -1311,7 +1311,7 @@ void MyAvatar::preRender(RenderArgs* renderArgs) {
_prevShouldDrawHead = shouldDrawHead; _prevShouldDrawHead = shouldDrawHead;
} }
const float RENDER_HEAD_CUTOFF_DISTANCE = 0.5f; const float RENDER_HEAD_CUTOFF_DISTANCE = 0.6f;
bool MyAvatar::cameraInsideHead() const { bool MyAvatar::cameraInsideHead() const {
const glm::vec3 cameraPosition = qApp->getCamera()->getPosition(); const glm::vec3 cameraPosition = qApp->getCamera()->getPosition();
@ -1361,6 +1361,37 @@ void MyAvatar::updateOrientation(float deltaTime) {
totalBodyYaw += _driveKeys[STEP_YAW]; 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 // update body orientation by movement inputs
setOrientation(getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f)))); setOrientation(getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f))));
@ -1537,6 +1568,16 @@ void MyAvatar::updatePosition(float deltaTime) {
// update _moving flag based on speed // update _moving flag based on speed
const float MOVING_SPEED_THRESHOLD = 0.01f; const float MOVING_SPEED_THRESHOLD = 0.01f;
_moving = speed > MOVING_SPEED_THRESHOLD; _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) { void MyAvatar::updateCollisionSound(const glm::vec3 &penetration, float deltaTime, float frequency) {

View file

@ -413,6 +413,9 @@ private:
AtRestDetector _hmdAtRestDetector; AtRestDetector _hmdAtRestDetector;
bool _lastIsMoving { false }; bool _lastIsMoving { false };
bool _hoverReferenceCameraFacingIsCaptured { false };
glm::vec3 _hoverReferenceCameraFacing; // hmd sensor space
}; };
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); 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 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include <QDir> #include <QtCore/QDir>
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QInputDialog>
#include <QMessageBox> #include <QMessageBox>
#include <QScriptValue> #include <QScriptValue>
#include <QScrollArea>
#include "Application.h" #include "Application.h"
#include "DomainHandler.h" #include "DomainHandler.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "Menu.h" #include "Menu.h"
#include "OffscreenUi.h" #include "OffscreenUi.h"
#include "ui/ModelsBrowser.h"
#include "WebWindowClass.h" #include "WebWindowClass.h"
#include "WindowScriptingInterface.h" #include "WindowScriptingInterface.h"
WindowScriptingInterface::WindowScriptingInterface() : WindowScriptingInterface::WindowScriptingInterface() {
_editDialog(NULL),
_nonBlockingFormActive(false),
_formResult(QDialog::Rejected)
{
const DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler(); const DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
connect(&domainHandler, &DomainHandler::connectedToDomain, this, &WindowScriptingInterface::domainChanged); connect(&domainHandler, &DomainHandler::connectedToDomain, this, &WindowScriptingInterface::domainChanged);
connect(qApp, &Application::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused); connect(qApp, &Application::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused);
@ -85,544 +76,31 @@ QScriptValue WindowScriptingInterface::getCursorPositionY() {
return QCursor::pos().y(); 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 /// Display an alert box
/// \param const QString& message message to display /// \param const QString& message message to display
/// \return QScriptValue::UndefinedValue /// \return QScriptValue::UndefinedValue
QScriptValue WindowScriptingInterface::showAlert(const QString& message) { void WindowScriptingInterface::alert(const QString& message) {
OffscreenUi::warning("", message); OffscreenUi::warning("", message);
return QScriptValue::UndefinedValue;
} }
/// Display a confirmation box with the options 'Yes' and 'No' /// Display a confirmation box with the options 'Yes' and 'No'
/// \param const QString& message message to display /// \param const QString& message message to display
/// \return QScriptValue `true` if 'Yes' was clicked, `false` otherwise /// \return QScriptValue `true` if 'Yes' was clicked, `false` otherwise
QScriptValue WindowScriptingInterface::showConfirm(const QString& message) { QScriptValue WindowScriptingInterface::confirm(const QString& message) {
bool confirm = false; return QScriptValue((QMessageBox::Yes == OffscreenUi::question("", message)));
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;
} }
/// Display a prompt with a text box /// Display a prompt with a text box
/// \param const QString& message message to display /// \param const QString& message message to display
/// \param const QString& defaultText default text in the text box /// \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. /// \return QScriptValue string text value in text box if the dialog was accepted, `null` otherwise.
QScriptValue WindowScriptingInterface::showPrompt(const QString& message, const QString& defaultText) { QScriptValue WindowScriptingInterface::prompt(const QString& message, const QString& defaultText) {
QInputDialog promptDialog(qApp->getWindow()); bool ok = false;
promptDialog.setWindowTitle(""); QString result = OffscreenUi::getText(nullptr, "", message, QLineEdit::Normal, defaultText, &ok);
promptDialog.setLabelText(message); return ok ? QScriptValue(result) : QScriptValue::NullValue;
promptDialog.setTextValue(defaultText);
promptDialog.setFixedSize(600, 200);
if (promptDialog.exec() == QDialog::Accepted) {
return QScriptValue(promptDialog.textValue());
}
return QScriptValue::NullValue;
} }
/// Display a file dialog. If `directory` is an invalid file or directory the browser will start at the current QString fixupPathForMac(const QString& directory) {
/// 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) {
// On OS X `directory` does not work as expected unless a file is included in the path, so we append a bogus // 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. // filename if the directory is valid.
QString path = ""; QString path = "";
@ -631,35 +109,31 @@ QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QS
fileInfo.setFile(directory, "__HIFI_INVALID_FILE__"); fileInfo.setFile(directory, "__HIFI_INVALID_FILE__");
path = fileInfo.filePath(); path = fileInfo.filePath();
} }
return path;
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;
} }
/// Display a browse window for S3 models /// Display an open file dialog. If `directory` is an invalid file or directory the browser will start at the current
/// \param const QString& nameFilter filter to filter filenames /// 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` /// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue`
QScriptValue WindowScriptingInterface::showS3Browse(const QString& nameFilter) { QScriptValue WindowScriptingInterface::browse(const QString& title, const QString& directory, const QString& nameFilter) {
ModelsBrowser browser(FSTReader::ENTITY_MODEL); QString path = fixupPathForMac(directory);
if (nameFilter != "") { QString result = OffscreenUi::getOpenFileName(nullptr, title, path, nameFilter);
browser.setNameFilter(nameFilter); return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result);
} }
QEventLoop loop;
connect(&browser, &ModelsBrowser::selected, &loop, &QEventLoop::quit); /// Display a save file dialog. If `directory` is an invalid file or directory the browser will start at the current
QMetaObject::invokeMethod(&browser, "browse", Qt::QueuedConnection); /// working directory.
loop.exec(); /// \param const QString& title title of the window
/// \param const QString& directory directory to start the file browser at
return browser.getSelectedFile(); /// \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() { int WindowScriptingInterface::getInnerWidth() {

View file

@ -12,12 +12,9 @@
#ifndef hifi_WindowScriptingInterface_h #ifndef hifi_WindowScriptingInterface_h
#define hifi_WindowScriptingInterface_h #define hifi_WindowScriptingInterface_h
#include <QObject> #include <QtCore/QObject>
#include <QScriptValue> #include <QtCore/QString>
#include <QString> #include <QtScript/QScriptValue>
#include <QFileDialog>
#include <QComboBox>
#include <QLineEdit>
class WebWindowClass; class WebWindowClass;
@ -41,61 +38,19 @@ public slots:
QScriptValue hasFocus(); QScriptValue hasFocus();
void setFocus(); void setFocus();
void raiseMainWindow(); void raiseMainWindow();
QScriptValue alert(const QString& message = ""); void alert(const QString& message = "");
QScriptValue confirm(const QString& message = ""); QScriptValue confirm(const QString& message = "");
QScriptValue form(const QString& title, QScriptValue array);
QScriptValue prompt(const QString& message = "", const QString& defaultText = ""); QScriptValue prompt(const QString& message = "", const QString& defaultText = "");
QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
QScriptValue save(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: signals:
void domainChanged(const QString& domainHostname); void domainChanged(const QString& domainHostname);
void inlineButtonClicked(const QString& name);
void nonBlockingFormClosed();
void svoImportRequested(const QString& url); void svoImportRequested(const QString& url);
void domainConnectionRefused(const QString& reason); void domainConnectionRefused(const QString& reason);
private slots: 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); 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 #endif // hifi_WindowScriptingInterface_h

View file

@ -22,6 +22,16 @@ AvatarHashMap::AvatarHashMap() {
connect(DependencyManager::get<NodeList>().data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged); connect(DependencyManager::get<NodeList>().data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged);
} }
QVector<QUuid> AvatarHashMap::getAvatarIdentifiers() {
QReadLocker locker(&_hashLock);
return _avatarHash.keys().toVector();
}
AvatarData* AvatarHashMap::getAvatar(QUuid avatarID) {
// Null/Default-constructed QUuids will return MyAvatar
return getAvatarBySessionID(avatarID).get();
}
bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range) { bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range) {
auto hashCopy = getHashCopy(); auto hashCopy = getHashCopy();
foreach(const AvatarSharedPointer& sharedAvatar, hashCopy) { foreach(const AvatarSharedPointer& sharedAvatar, hashCopy) {

View file

@ -34,6 +34,12 @@ public:
AvatarHash getHashCopy() { QReadLocker lock(&_hashLock); return _avatarHash; } AvatarHash getHashCopy() { QReadLocker lock(&_hashLock); return _avatarHash; }
int size() { return _avatarHash.size(); } int size() { return _avatarHash.size(); }
// Currently, your own avatar will be included as the null avatar id.
Q_INVOKABLE QVector<QUuid> getAvatarIdentifiers();
Q_INVOKABLE AvatarData* getAvatar(QUuid avatarID);
virtual AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID) { return findAvatar(sessionID); }
signals: signals:
void avatarAddedEvent(const QUuid& sessionUUID); void avatarAddedEvent(const QUuid& sessionUUID);
void avatarRemovedEvent(const QUuid& sessionUUID); void avatarRemovedEvent(const QUuid& sessionUUID);

View file

@ -12,6 +12,8 @@
#include <QtCore/QDir> #include <QtCore/QDir>
#include <QtCore/QFile> #include <QtCore/QFile>
#include <QtCore/QDebug>
#include <QtCore/QRegularExpression>
QUrl FileDialogHelper::home() { QUrl FileDialogHelper::home() {
@ -26,7 +28,11 @@ QString FileDialogHelper::urlToPath(const QUrl& url) {
return url.toLocalFile(); 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(); return QFile(path).exists();
} }
@ -38,3 +44,62 @@ QUrl FileDialogHelper::pathToUrl(const QString& path) {
return QUrl::fromLocalFile(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 QUrl home();
Q_INVOKABLE QStringList standardPath(StandardLocation location); Q_INVOKABLE QStringList standardPath(StandardLocation location);
Q_INVOKABLE QStringList drives();
Q_INVOKABLE QString urlToPath(const QUrl& url); 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 validPath(const QString& path);
Q_INVOKABLE bool validFolder(const QString& path); Q_INVOKABLE bool validFolder(const QString& path);
Q_INVOKABLE QUrl pathToUrl(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); OffscreenQmlSurface::create(context);
auto rootContext = getRootContext(); auto rootContext = getRootContext();
rootContext->setContextProperty("OffscreenUi", this);
rootContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags()); rootContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags());
rootContext->setContextProperty("urlHandler", new UrlHandler()); rootContext->setContextProperty("urlHandler", new UrlHandler());
rootContext->setContextProperty("fileDialogHelper", new FileDialogHelper()); rootContext->setContextProperty("fileDialogHelper", new FileDialogHelper());
@ -219,7 +220,7 @@ QQuickItem* OffscreenUi::createMessageBox(QMessageBox::Icon icon, const QString&
return qvariant_cast<QQuickItem*>(result); return qvariant_cast<QQuickItem*>(result);
} }
QMessageBox::StandardButton OffscreenUi::waitForMessageBoxResult(QQuickItem* messageBox) { int OffscreenUi::waitForMessageBoxResult(QQuickItem* messageBox) {
if (!messageBox) { if (!messageBox) {
return QMessageBox::NoButton; return QMessageBox::NoButton;
} }
@ -241,7 +242,7 @@ QMessageBox::StandardButton OffscreenUi::messageBox(QMessageBox::Icon icon, cons
return result; 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, 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) { QString OffscreenUi::fileOpenDialog(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
QString result; QString result;
@ -497,28 +518,40 @@ QString OffscreenUi::fileOpenDialog(const QString& caption, const QString& dir,
map.insert("dir", QUrl::fromLocalFile(dir)); map.insert("dir", QUrl::fromLocalFile(dir));
map.insert("filter", filter); map.insert("filter", filter);
map.insert("options", static_cast<int>(options)); map.insert("options", static_cast<int>(options));
return fileDialog(map);
}
QVariant buildDialogResult; QString OffscreenUi::fileSaveDialog(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
bool invokeResult = QMetaObject::invokeMethod(_desktop, "fileOpenDialog", if (QThread::currentThread() != thread()) {
Q_RETURN_ARG(QVariant, buildDialogResult), QString result;
Q_ARG(QVariant, QVariant::fromValue(map))); QMetaObject::invokeMethod(this, "fileSaveDialog", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QString, result),
if (!invokeResult) { Q_ARG(QString, caption),
qWarning() << "Failed to create file open dialog"; Q_ARG(QString, dir),
return QString(); Q_ARG(QString, filter),
Q_ARG(QString*, selectedFilter),
Q_ARG(QFileDialog::Options, options));
return result;
} }
QVariant result = FileDialogListener(qvariant_cast<QQuickItem*>(buildDialogResult)).waitForResult(); // FIXME support returning the selected filter... somehow?
if (!result.isValid()) { QVariantMap map;
return QString(); map.insert("caption", caption);
} map.insert("dir", QUrl::fromLocalFile(dir));
qDebug() << result.toString(); map.insert("filter", filter);
return result.toUrl().toLocalFile(); 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) { 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); 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" #include "OffscreenUi.moc"

View file

@ -47,7 +47,7 @@ public:
// Must be called from the main thread // Must be called from the main thread
QQuickItem* createMessageBox(QMessageBox::Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton); QQuickItem* createMessageBox(QMessageBox::Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton);
// Must be called from the main thread // 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 /// Same design as QMessageBox::critical(), will block, returns result
static QMessageBox::StandardButton critical(void* ignored, const QString& title, const QString& text, static QMessageBox::StandardButton critical(void* ignored, const QString& title, const QString& text,
@ -87,10 +87,13 @@ public:
QMessageBox::StandardButtons buttons = QMessageBox::Ok, QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); 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 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 // 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); 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 // 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); 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: private:
QString fileDialog(const QVariantMap& properties);
QQuickItem* _desktop { nullptr }; QQuickItem* _desktop { nullptr };
QQuickItem* _toolWindow { nullptr }; QQuickItem* _toolWindow { nullptr };
}; };

View file

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