From c1d45b0c784c2b94407a3d16189baa50439d9ae2 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Tue, 2 Feb 2016 17:09:58 -0800 Subject: [PATCH 1/7] Remove declarative form creation from WindowScriptingInterface --- examples/edit.js | 2 - examples/libraries/entityPropertyDialogBox.js | 457 ------------------ .../scripting/WindowScriptingInterface.cpp | 431 +---------------- .../src/scripting/WindowScriptingInterface.h | 29 +- 4 files changed, 8 insertions(+), 911 deletions(-) delete mode 100644 examples/libraries/entityPropertyDialogBox.js diff --git a/examples/edit.js b/examples/edit.js index cc5921efd1..3e46160522 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -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(); diff --git a/examples/libraries/entityPropertyDialogBox.js b/examples/libraries/entityPropertyDialogBox.js deleted file mode 100644 index b04d797b77..0000000000 --- a/examples/libraries/entityPropertyDialogBox.js +++ /dev/null @@ -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; - -}()); diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 73c0098995..5d5837ebd8 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -27,11 +27,7 @@ #include "WindowScriptingInterface.h" -WindowScriptingInterface::WindowScriptingInterface() : - _editDialog(NULL), - _nonBlockingFormActive(false), - _formResult(QDialog::Rejected) -{ +WindowScriptingInterface::WindowScriptingInterface() { const DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); connect(&domainHandler, &DomainHandler::connectedToDomain, this, &WindowScriptingInterface::domainChanged); connect(qApp, &Application::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused); @@ -98,14 +94,6 @@ QScriptValue WindowScriptingInterface::confirm(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, @@ -139,32 +127,6 @@ QScriptValue WindowScriptingInterface::s3Browse(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 @@ -217,402 +179,23 @@ void WindowScriptingInterface::inlineButtonClicked() { emit inlineButtonClicked(name); } -QString WindowScriptingInterface::jsRegExp2QtRegExp(QString string) { +QString WindowScriptingInterface::jsRegExp2QtRegExp(const 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 /// \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()); + bool ok = false; + QString result = OffscreenUi::getText(nullptr, "", message, QLineEdit::Normal, defaultText, &ok); + if (!ok) { + return QScriptValue::NullValue; } - return QScriptValue::NullValue; + return QScriptValue(result); } /// Display a file dialog. If `directory` is an invalid file or directory the browser will start at the current diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 4b287a24e9..225628b698 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -43,59 +43,32 @@ public slots: void raiseMainWindow(); QScriptValue 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 _combos; - QVector _checks; - QVector _edits; - QVector _directories; + QString jsRegExp2QtRegExp(const QString& string); }; #endif // hifi_WindowScriptingInterface_h From 0a99086617c7b8152680a6a3ca9d05caf13f572d Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Tue, 2 Feb 2016 18:14:33 -0800 Subject: [PATCH 2/7] Moving to QML based dialogs --- .../scripting/WindowScriptingInterface.cpp | 167 +++--------------- .../src/scripting/WindowScriptingInterface.h | 26 +-- libraries/ui/src/OffscreenUi.cpp | 4 + libraries/ui/src/OffscreenUi.h | 1 + 4 files changed, 38 insertions(+), 160 deletions(-) diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 5d5837ebd8..3e8d0d0360 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -9,20 +9,15 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include -#include -#include -#include +#include #include #include -#include #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" @@ -81,131 +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::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; -} - - /// 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(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(sender()); - QString name = button->property("name").toString(); - emit inlineButtonClicked(name); -} - -QString WindowScriptingInterface::jsRegExp2QtRegExp(const 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. +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) { +QScriptValue WindowScriptingInterface::prompt(const QString& message, const QString& defaultText) { bool ok = false; QString result = OffscreenUi::getText(nullptr, "", message, QLineEdit::Normal, defaultText, &ok); - if (!ok) { - return QScriptValue::NullValue; - } - return QScriptValue(result); + 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 = ""; @@ -214,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() { diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 225628b698..d00e63b49e 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -12,12 +12,9 @@ #ifndef hifi_WindowScriptingInterface_h #define hifi_WindowScriptingInterface_h -#include -#include -#include -#include -#include -#include +#include +#include +#include class WebWindowClass; @@ -41,34 +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 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 = ""); signals: void domainChanged(const QString& domainHostname); - void inlineButtonClicked(const QString& name); void svoImportRequested(const QString& url); void domainConnectionRefused(const QString& reason); private slots: - QScriptValue showAlert(const QString& message); - QScriptValue showConfirm(const QString& message); - 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 chooseDirectory(); - void inlineButtonClicked(); - WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height); - -private: - QString jsRegExp2QtRegExp(const QString& string); }; #endif // hifi_WindowScriptingInterface_h diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 1d471d5419..3ac12d014f 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -520,5 +520,9 @@ QString OffscreenUi::getOpenFileName(void* ignored, const QString &caption, cons return DependencyManager::get()->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 QFileDialog::getSaveFileName((QWidget*)ignored, caption, dir, filter, selectedFilter, options); +} + #include "OffscreenUi.moc" diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index ec5ba433cc..73a9ca7c47 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -91,6 +91,7 @@ public: Q_INVOKABLE QString fileOpenDialog(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); + 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 From 320c4d27b52b1a14e9892c8a26387d9447229295 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 3 Feb 2016 10:33:11 -0800 Subject: [PATCH 3/7] Force Qt (rather than OS) rendering in a number of places --- interface/resources/qml/controls/CheckBox.qml | 5 +++-- interface/resources/qml/controls/TextField.qml | 7 +++++++ interface/resources/qml/dialogs/FileDialog.qml | 3 +++ interface/resources/qml/dialogs/QueryDialog.qml | 2 +- interface/resources/qml/dialogs/RunningScripts.qml | 4 ++-- .../resources/qml/dialogs/preferences/AvatarPreference.qml | 2 ++ .../qml/dialogs/preferences/BrowsablePreference.qml | 2 ++ .../qml/dialogs/preferences/CheckBoxPreference.qml | 1 + .../qml/dialogs/preferences/EditablePreference.qml | 2 ++ .../resources/qml/hifi/dialogs/AvatarAttachmentsDialog.qml | 2 ++ .../resources/qml/hifi/dialogs/ModelBrowserDialog.qml | 2 ++ .../resources/qml/hifi/dialogs/attachments/Attachment.qml | 3 ++- 12 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 interface/resources/qml/controls/TextField.qml diff --git a/interface/resources/qml/controls/CheckBox.qml b/interface/resources/qml/controls/CheckBox.qml index fe836a0e89..91586299a7 100644 --- a/interface/resources/qml/controls/CheckBox.qml +++ b/interface/resources/qml/controls/CheckBox.qml @@ -10,7 +10,8 @@ Original.CheckBox { } label: Text { text: control.text + renderType: Text.QtRendering } } - -} \ No newline at end of file + +} diff --git a/interface/resources/qml/controls/TextField.qml b/interface/resources/qml/controls/TextField.qml new file mode 100644 index 0000000000..df536a1de5 --- /dev/null +++ b/interface/resources/qml/controls/TextField.qml @@ -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 } +} diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 96a0c95e88..3a26c3ca27 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -2,6 +2,7 @@ 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 ".." import "../windows" @@ -82,6 +83,7 @@ ModalWindow { 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; @@ -181,6 +183,7 @@ ModalWindow { 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 diff --git a/interface/resources/qml/dialogs/QueryDialog.qml b/interface/resources/qml/dialogs/QueryDialog.qml index fecd69a4c6..948bbb1295 100644 --- a/interface/resources/qml/dialogs/QueryDialog.qml +++ b/interface/resources/qml/dialogs/QueryDialog.qml @@ -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 diff --git a/interface/resources/qml/dialogs/RunningScripts.qml b/interface/resources/qml/dialogs/RunningScripts.qml index 8b148c6bef..c9c9062bbd 100644 --- a/interface/resources/qml/dialogs/RunningScripts.qml +++ b/interface/resources/qml/dialogs/RunningScripts.qml @@ -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 diff --git a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml index 1375fc392a..a92392799d 100644 --- a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml +++ b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml @@ -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 diff --git a/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml b/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml index 7d310be468..637cc6c02c 100644 --- a/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml +++ b/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml @@ -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 diff --git a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml index ab37f30427..f28f3ab90b 100644 --- a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml @@ -1,5 +1,6 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 +import "../../controls" Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/EditablePreference.qml b/interface/resources/qml/dialogs/preferences/EditablePreference.qml index 53dc7ed5d0..ddd5600bca 100644 --- a/interface/resources/qml/dialogs/preferences/EditablePreference.qml +++ b/interface/resources/qml/dialogs/preferences/EditablePreference.qml @@ -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 diff --git a/interface/resources/qml/hifi/dialogs/AvatarAttachmentsDialog.qml b/interface/resources/qml/hifi/dialogs/AvatarAttachmentsDialog.qml index 252e4c629e..5de0864df5 100644 --- a/interface/resources/qml/hifi/dialogs/AvatarAttachmentsDialog.qml +++ b/interface/resources/qml/hifi/dialogs/AvatarAttachmentsDialog.qml @@ -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 } diff --git a/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml index 252e4c629e..b103279d4c 100644 --- a/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml +++ b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml @@ -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 diff --git a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml index 31a1895e58..8dc5d8ba4b 100644 --- a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml +++ b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml @@ -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 } From 8f85abfec8c214eea0aeaeffe5b8db2756ee3524 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 3 Feb 2016 10:59:50 -0800 Subject: [PATCH 4/7] Support 'naked' selection globs in file dialog --- .../qml/dialogs/fileDialog/FileTypeSelection.qml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml b/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml index a3ab652e32..57ad2028ad 100644 --- a/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml +++ b/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml @@ -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(" "); } From 60d97c45affa260571218f9e732f6b4df9b7151a Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 3 Feb 2016 14:49:50 -0800 Subject: [PATCH 5/7] Support save file dialog --- interface/resources/qml/desktop/Desktop.qml | 3 +- .../resources/qml/dialogs/FileDialog.qml | 84 ++++++++++++++++--- .../resources/qml/dialogs/MessageDialog.qml | 4 + libraries/ui/src/FileDialogHelper.cpp | 52 +++++++++++- libraries/ui/src/FileDialogHelper.h | 6 ++ libraries/ui/src/OffscreenUi.cpp | 63 ++++++++++---- libraries/ui/src/OffscreenUi.h | 8 +- 7 files changed, 186 insertions(+), 34 deletions(-) diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 7fb5cd3127..0286c45ac3 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -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); diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 3a26c3ca27..142ed198c0 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -3,6 +3,7 @@ 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" @@ -39,7 +40,6 @@ ModalWindow { 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 @@ -124,6 +124,8 @@ ModalWindow { currentSelectionIsFolder = fileTableView.model.isFolder(row); if (root.selectDirectory || !currentSelectionIsFolder) { currentSelection.text = helper.urlToPath(currentSelectionUrl); + } else { + currentSelection.text = "" } } @@ -175,8 +177,7 @@ ModalWindow { if (isFolder) { fileTableView.model.folder = file } else { - root.selectedFile(file); - root.destroy(); + okAction.trigger(); } } } @@ -185,8 +186,10 @@ ModalWindow { 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 { @@ -206,25 +209,82 @@ 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: { + if (root.saveDialog) { + // 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; + } + } + + selectedFile(d.currentSelectionUrl); + root.destroy() + } else { + selectedFile(d.currentSelectionUrl); + root.destroy() + } + + } + + } + + Action { + id: cancelAction + text: "Cancel" + onTriggered: { canceled(); root.visible = false; } + } } Keys.onPressed: { diff --git a/interface/resources/qml/dialogs/MessageDialog.qml b/interface/resources/qml/dialogs/MessageDialog.qml index ca00da1aa3..3b7cc2c9a8 100644 --- a/interface/resources/qml/dialogs/MessageDialog.qml +++ b/interface/resources/qml/dialogs/MessageDialog.qml @@ -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 diff --git a/libraries/ui/src/FileDialogHelper.cpp b/libraries/ui/src/FileDialogHelper.cpp index f8a0929702..f1cbb22f56 100644 --- a/libraries/ui/src/FileDialogHelper.cpp +++ b/libraries/ui/src/FileDialogHelper.cpp @@ -12,6 +12,8 @@ #include #include +#include +#include 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,47 @@ 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) { + return QFileInfo(url.toLocalFile()).isWritable(); +} diff --git a/libraries/ui/src/FileDialogHelper.h b/libraries/ui/src/FileDialogHelper.h index edb702eeda..0142473533 100644 --- a/libraries/ui/src/FileDialogHelper.h +++ b/libraries/ui/src/FileDialogHelper.h @@ -48,9 +48,15 @@ public: Q_INVOKABLE QUrl home(); Q_INVOKABLE QStringList standardPath(StandardLocation location); 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); }; diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 3ac12d014f..fa40fedb9b 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -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(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(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(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,23 +518,31 @@ QString OffscreenUi::fileOpenDialog(const QString& caption, const QString& dir, map.insert("dir", QUrl::fromLocalFile(dir)); map.insert("filter", filter); map.insert("options", static_cast(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(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(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) { @@ -521,7 +550,7 @@ QString OffscreenUi::getOpenFileName(void* ignored, const QString &caption, cons } QString OffscreenUi::getSaveFileName(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) { - return QFileDialog::getSaveFileName((QWidget*)ignored, caption, dir, filter, selectedFilter, options); + return DependencyManager::get()->fileSaveDialog(caption, dir, filter, selectedFilter, options); } diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index 73a9ca7c47..de479853f3 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -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,12 @@ 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); @@ -105,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 }; }; From 64597f9d9f86c71dfe2d6e3a9b915a04d60d6032 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Thu, 4 Feb 2016 13:32:52 -0800 Subject: [PATCH 6/7] Fixing save dialog issues saving new files --- .../resources/qml/dialogs/FileDialog.qml | 91 ++++++++++--------- libraries/ui/src/FileDialogHelper.cpp | 9 +- 2 files changed, 58 insertions(+), 42 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 142ed198c0..a482b76c70 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -229,55 +229,64 @@ ModalWindow { 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) { - // 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 (!root.saveDialog) { + selectedFile(d.currentSelectionUrl); + root.destroy() + return; + } - if (!selection) { - desktop.messageBox({ icon: OriginalDialogs.StandardIcon.Warning, text: "Unable to parse selection" }) - return; - } - if (helper.urlIsDir(selection)) { - root.dir = selection; - currentSelection.text = ""; - 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); - // 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 (!selection) { + desktop.messageBox({ icon: OriginalDialogs.StandardIcon.Warning, text: "Unable to parse 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; - } - } + if (helper.urlIsDir(selection)) { + root.dir = selection; + currentSelection.text = ""; + return; + } - selectedFile(d.currentSelectionUrl); - root.destroy() - } else { - selectedFile(d.currentSelectionUrl); - root.destroy() - } + // 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 { diff --git a/libraries/ui/src/FileDialogHelper.cpp b/libraries/ui/src/FileDialogHelper.cpp index f1cbb22f56..56b9695444 100644 --- a/libraries/ui/src/FileDialogHelper.cpp +++ b/libraries/ui/src/FileDialogHelper.cpp @@ -86,5 +86,12 @@ bool FileDialogHelper::urlExists(const QUrl& url) { } bool FileDialogHelper::urlIsWritable(const QUrl& url) { - return QFileInfo(url.toLocalFile()).isWritable(); + 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(); } From dc0e038535485d921ebcf051b72744487e9f2464 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Thu, 4 Feb 2016 13:51:43 -0800 Subject: [PATCH 7/7] Add drive selection to the file dialog --- .../resources/qml/dialogs/FileDialog.qml | 19 +++++++++++++++++++ libraries/ui/src/FileDialogHelper.cpp | 8 ++++++++ libraries/ui/src/FileDialogHelper.h | 1 + tests/ui/qml/main.qml | 8 +------- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index a482b76c70..10dd4897e3 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -36,6 +36,7 @@ ModalWindow { // Set from OffscreenUi::getOpenFile() property int options; // <-- FIXME unused + property bool selectDirectory: false; property bool showHidden: false; // FIXME implement @@ -43,10 +44,18 @@ ModalWindow { 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" @@ -78,6 +87,16 @@ ModalWindow { size: 32 onClicked: d.navigateHome(); } + + VrControls.ComboBox { + id: drivesSelector + width: 48 + height: homeButton.height + model: drives + visible: drives.length > 1 + currentIndex: 0 + + } } TextField { diff --git a/libraries/ui/src/FileDialogHelper.cpp b/libraries/ui/src/FileDialogHelper.cpp index 56b9695444..82ad877573 100644 --- a/libraries/ui/src/FileDialogHelper.cpp +++ b/libraries/ui/src/FileDialogHelper.cpp @@ -95,3 +95,11 @@ bool FileDialogHelper::urlIsWritable(const QUrl& url) { // 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; +} diff --git a/libraries/ui/src/FileDialogHelper.h b/libraries/ui/src/FileDialogHelper.h index 0142473533..2119b77917 100644 --- a/libraries/ui/src/FileDialogHelper.h +++ b/libraries/ui/src/FileDialogHelper.h @@ -47,6 +47,7 @@ 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); diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index d20b580b5a..a55f042227 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -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: {